リンクされた質問からのバリーの例では:
template<typename T, typename = void>
struct has_to_string
: std::false_type { };
template<typename T>
struct has_to_string<T,
void_t<decltype(std::to_string(std::declval<T>()))>
>
: std::true_type { };
void_t
によって推定される型を変換するために使用されるだけdecltype
でvoid
、デフォルトの引数がプライマリテンプレート定義に一致します。SFINAE はすべてdecltype
式によって処理されます。次のように簡単に実行できます。
//use , void() instead of wrapping in void_t
//this uses the comma operator to check the type of the to_string call, then change the type to void
decltype(std::to_string(std::declval<T>()), void())
以前のバージョンははるかに読みやすく、void_t
動作する必要はありませんdecltype
。
が実装で利用可能な場合void_t
は、再定義する必要はありません。標準化されると、標準の他のエイリアス テンプレートと同じように利用できるようになります。
このように考えてください:有効なオーバーロードを持つT
isの場合、演繹は次のようになります。int
std::to_string
has_to_string<int>
->has_to_string<int,void>
デフォルトの引数のため。それではhas_to_string
、これらの引数での特殊化を探してみましょう。
template<typename T>
struct has_to_string<T,
void_t<decltype(std::to_string(std::declval<T>()))>
>
: std::true_type { };
さて、それはT
いくつかの依存型の部分的な特殊化です。そのタイプを考えてみましょう:
void_t<decltype(std::to_string(std::declval<T>()))>
//std::to_string(int&&) is valid and returns a std::string
void_t<std::string>
//void_t changes types to void
void
これで、専門化は次のようになります。
template<>
struct has_to_string<int,void>
: std::true_type { };
これは のインスタンス化と一致するhas_string<int,void>
ため、 からhas_to_string<int>
継承しstd::true_type
ます。
T
が のときを考えてみてくださいstruct Foo{};
。もう一度、その依存型を考えてみましょう。
void_t<decltype(std::to_string(std::declval<T>()))>
//wait, std::to_string(Foo&&) doesn't exist
//discard that specialization
その特殊化を破棄すると、プライマリ テンプレートに戻ります。
template<typename T, typename = void>
struct has_to_string
: std::false_type { };
からhas_to_string<Foo>
継承しstd::false_type
ます。