了解别名模板

时间:2015-05-18 11:47:59

标签: c++ templates alias enable-if c++17

我问了一个question,它有几个对代码的引用:

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

我相信我一般会误解alias templates

您为什么不评估您在enable_if_tconditional_t声明中传递到别名模板的模板参数?

上面的代码是否只是一次对多个模板参数执行enable_if_t

其次,我认为我对void_t的作用有一个特定的误解。 This comment声明C ++ 17标准定义了void_t。这是我不能得到的:

Isn&#39; t void_t只是一个任意名称?如果我仍然需要在我计划使用template <typename...> using void_t = void;的任何地方定义void_t,那么标准化任意名称的目的是什么?

2 个答案:

答案 0 :(得分:8)

在Barry的链接问题示例中:

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,则无需重新定义。当它标准化时,它将像标准中的任何其他别名模板一样可用。

以这种方式思考:如果Tint,其有效期为std::to_string,则扣除将如下所示:

has_to_string<int> - &gt; 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

现在在Tstruct 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

答案 1 :(得分:3)

我不认为所显示的示例确实显示了void_t有什么好处,因为它只显示了一个用例,但是当你看到

template<typename T>
struct has_to_string<T, 
    void_t<decltype(std::to_string(std::declval<T>()))>
    > 
: std::true_type { };

没什么不同
template<typename T>
struct has_to_string<T, 
    decltype(std::to_string(std::declval<T>()), void())
    > 
: std::true_type { };

对于这个陈述:

  

以前的版本更容易阅读,void_t不需要decltype才能使用。

我认为可读性的优势非常小,第二部分没有意义,当decltype不起作用时,SFINAE会按预期启动。

void_t更有用的一个例子是提案中的一个例子:

// primary template handles types that have no nested ::type member
template< class, class = void_t<> >
struct has_type_member
: std::false_type { };

// specialization recognizes types that do have a nested ::type member
template< class T >
struct has_type_member<T, void_t<typename T::type>>
: std::true_type { }

正如您所看到的,即使主模板使用void_t来提高可读性,因为它现在与专业化相匹配。这不是绝对必要的,但我喜欢它。当您考虑替代方案时,真正的力量就来了。没有void_t,专业化现在变得更加复杂:

template< class T >
struct has_type_member<T, decltype(typename T::type, void())>
: std::true_type { }

不能用作T::type命名类型,而不是表达式。因此,您需要

template< class T >
struct has_type_member<T, decltype(std::declval<typename T::type>(), void())>
: std::true_type { }

整个表达式变得更长,更棘手,并且可能会遇到您忘记处理的边缘情况。这是void_t真正有用的地方,其他用途只是一个小改进,它们可以提高一致性。