了解别名模板
Understanding Alias Templates
我问了一个 有几个参考代码:
template <typename...>
using void_t = void;
我相信我有一个普遍的误解alias templates:
您为什么不直接评估您在 enable_if_t
或 conditional_t
语句中传递给别名模板的任何模板参数?
上面的代码只是一次对多个模板参数执行 enable_if_t
吗?
其次,我认为我对void_t
的作用存在特定的误解。 指出 C++17 标准定义了 void_t
。这是我没有得到的:
void_t
不就是随便起的名字吗?如果我仍然必须在计划使用 void_t
的任何地方定义 template <typename...> using void_t = void;
,那么标准化任意名称的意义何在?
在你的链接问题中 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
,以便它与 primary 的默认参数相匹配模板定义。 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
是 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
。
我认为显示的示例并没有真正说明 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 { };
对于这个语句:
The former version is much easier to read and void_t
doesn't require decltype
to work.
我认为在可读性方面的优势很小,第二部分没有意义,当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
真正有用的地方,其他用途只是一个小的改进,它们提高了一致性。
我问了一个
template <typename...>
using void_t = void;
我相信我有一个普遍的误解alias templates:
您为什么不直接评估您在 enable_if_t
或 conditional_t
语句中传递给别名模板的任何模板参数?
上面的代码只是一次对多个模板参数执行 enable_if_t
吗?
其次,我认为我对void_t
的作用存在特定的误解。 void_t
。这是我没有得到的:
void_t
不就是随便起的名字吗?如果我仍然必须在计划使用 void_t
的任何地方定义 template <typename...> using void_t = void;
,那么标准化任意名称的意义何在?
在你的链接问题中 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
,以便它与 primary 的默认参数相匹配模板定义。 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
是 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
。
我认为显示的示例并没有真正说明 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 { };
对于这个语句:
The former version is much easier to read and
void_t
doesn't requiredecltype
to work.
我认为在可读性方面的优势很小,第二部分没有意义,当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
真正有用的地方,其他用途只是一个小的改进,它们提高了一致性。