C++ void_t


Esempio

C ++ 11

void_t è una meta-funzione che mappa qualsiasi (numero di) tipi di tipo void . Lo scopo principale di void_t è facilitare la scrittura di caratteri tipografici.

std::void_t sarà parte di C ++ 17, ma fino ad allora, è estremamente semplice implementare:

template <class...> using void_t = void;

Alcuni compilatori richiedono un'implementazione leggermente diversa:

template <class...>
struct make_void { using type = void; };

template <typename... T>
using void_t = typename make_void<T...>::type;

L'applicazione principale di void_t è la scrittura di tratti di tipo che controllano la validità di una dichiarazione. Ad esempio, controlliamo se un tipo ha una funzione membro foo() che non accetta argomenti:

template <class T, class=void>
struct has_foo : std::false_type {};

template <class T>
struct has_foo<T, void_t<decltype(std::declval<T&>().foo())>> : std::true_type {};

Come funziona? Quando provo a istanziare has_foo<T>::value , ciò farà sì che il compilatore cerchi di cercare la migliore specializzazione per has_foo<T, void> . Abbiamo due opzioni: la primaria, e questa secondaria che implica dover istanziare quella espressione sottostante:

  • Se T ha una funzione membro foo() , allora qualsiasi tipo che restituisce viene convertito in void , e la specializzazione è preferito al primario basati sul modello regole ordinamento parziale. Quindi has_foo<T>::value sarà true
  • Se T non ha una funzione membro (o richiede più di un argomento), la sostituzione non riesce per la specializzazione e abbiamo solo il modello principale su cui fare il fallback. Quindi, has_foo<T>::value è false .

Un caso più semplice:

template<class T, class=void>
struct can_reference : std::false_type {};

template<class T>
struct can_reference<T, std::void_t<T&>> : std::true_type {};

questo non usa std::declval o decltype .

Si può notare un modello comune di un argomento vuoto. Possiamo tenerne conto:

struct details {
  template<template<class...>class Z, class=void, class...Ts>
  struct can_apply:
    std::false_type
  {};
  template<template<class...>class Z, class...Ts>
  struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...>:
    std::true_type
  {};
};

template<template<class...>class Z, class...Ts>
using can_apply = details::can_apply<Z, void, Ts...>;

che nasconde l'uso di std::void_t e fa can_apply come un indicatore se il tipo fornito come primo argomento template è ben formato dopo aver sostituito gli altri tipi in esso. Gli esempi precedenti possono ora essere riscritti usando can_apply come:

template<class T>
using ref_t = T&;

template<class T>
using can_reference = can_apply<ref_t, T>;    // Is T& well formed for T?

e:

template<class T>
using dot_foo_r = decltype(std::declval<T&>().foo());

template<class T>
using can_dot_foo = can_apply< dot_foo_r, T >;    // Is T.foo() well formed for T?

che sembra più semplice delle versioni originali.

Ci sono post-C ++ 17 proposte di std tratti simili a can_apply .

L'utilità di void_t stata scoperta da Walter Brown. Ha tenuto una meravigliosa presentazione al CppCon 2016.