在C++17
,void_t
允许使用class
/ struct
模板轻松完成SFINAE:
template <class T, class = void>
struct test {
static constexpr auto text = "general case";
};
template <class T>
struct test<T, std::void_t<decltype(std::begin(std::declval<T>())>> {
static constexpr auto text = "has begin iterator";
};
什么是void_t
内部是一种类型。我的问题是:当void_t
内部的类型特征时,如何做同样的事情。使用enable_if
效果很好:
template <class T>
struct test<T, std::void_t<std::enable_if_t<std::is_class_v<T>>> {
static constexpr auto text = "is a class";
};
有没有更短/更优雅的方式来写这个,或者“正确的方式”去做,真的要结合void_t
和enable_if
?
std::void_t
的一个重要观点是可变的
// ................VVV <- is variadic
template <typename ...>
using void_t = void;
因此,当您必须检查多种类型时允许SFINAE工作,并且只允许其中一个类型失败时允许软故障。
如果你只需要检查一个值,你必须用std::enable_if
(或类似的类型特征)检查它,我没有理由与std::void_t
一起使用它。
所以,在你的例子中,“正确的方式”(恕我直言)是避免使用std::void_t
template <class T>
struct test<T, std::enable_if_t<std::is_class_v<T>>
{ static constexpr auto text = "is a class"; };
同样在使用单个decltype()
的情况下,我更喜欢旧的方式(但我想这是个人品味的问题)
template <class T>
struct test<T, decltype(std::begin(std::declval<T>(), void())>
{ static constexpr auto text = "has begin iterator"; };
你似乎不明白为什么std::void_t
是必要的。我们来解决这个问题:)
在你的第一个例子中,如果你不使用std::void_t
,那么部分特化将永远不会被选中,因为decltype
将评估为某种类型T
不是void
,因此它不会与部分特化相匹配并且会回到一般情况。现在,如果您知道函数将始终返回相同类型,则可以随时更改主模板中的默认参数,但这种方式更难以更改。 (你可以看看我给另一个可能有助于理解这个问题的问题的answer)。
这就是std::void_t
被引入的原因。 std::void_t
只是一种身份类型特征,无论如何都是void
。这意味着在你的第一个例子中,无论decltype
评估的是什么,第二个模板参数将是void
,因此匹配并且如果decltype
格式正确则将选择特化。
std::enable_if_t
是有效的,默认情况下是void
,当且仅当其中的条件评估为true时。这意味着std::enable_if_t
已经返回了void
,无论如何,因此你不需要std::void_t
将其“转换”为void
,因为它已经是void
。