进一步了解 type_traits
Understanding more about type_traits
设置
我昨天问了一个关于模板方法重载和使用类型特征解决问题的问题。我收到了一些很好的答案,他们引导我找到了解决方案。这个解决方案让我阅读了更多书。
我登陆了 Fluent CPP 的一个页面 -- https://www.fluentcpp.com/2018/05/18/make-sfinae-pretty-2-hidden-beauty-sfinae/ 这很有趣,然后我听了 Boccara 先生提到的 Stephen Dewhurst 演讲。这一切都令人着迷。
我现在想多了解一点。在昨天的回答中,我得到了这个解决方案:
template< class Function, class... Args,
std::enable_if_t<std::is_invocable_v<Function, Args...>, std::nullptr_t> = nullptr>
explicit MyClass( const std::string & theName, Function&& f, Args&&... args )
: name(theName)
{
runner(f, args...);
}
替代答案
在阅读了 CPP Fluent post 并观看了演讲后,我得出了这个最终解决方案:
template< class Function, class... Args>
using IsInvocable = std::enable_if_t < std::is_invocable_v<Function, Args...> >;
template< class Function, class... Args, typename = IsInvocable<Function, Args...> >
explicit ThreadHandle( const std::string & name, Function && f, Args &&... args ) {
startWithName(name, f, args...);
}
第一位只是将一些语法移动到一个通用的包含文件中,但总的来说,这更简单。我认为这很干净,几乎不需要解释,即使对于不熟悉使用类型特征的人也是如此。
问题
我想知道的是这个。我收到的所有三个答案都使用了更复杂的 enable_if_t
形式,如下所示:
std::enable_if_t<std::is_invocable_v<Function, Args...>, std::nullptr_t> = nullptr>
而且我不确定如果我可以这样做,他们为什么会这样做:
std::enable_if_t< std::is_invocable_v < Function, Args... > >
有影响吗?还是这仅仅是 C++11 更复杂的问题,而现在 C++14 和 17 允许更简单的形式?也许回复的人只是通过向我展示完整的表格来帮助我。
更让我困惑的是,其中一个答案是这样的:
std::enable_if_t<!std::is_convertible_v<Function, std::string>, bool> = true>
另一个人这样做了:
std::enable_if_t<std::is_invocable_v<Function, Args...>, int> = 0>
我也不太明白这些含义。
任何帮助克服障碍都会很棒。我想会有一些情况我会想要更复杂的版本,所以更好地理解它会很好。
词汇
// template-head
template<typename T = T{}>
// ^^^^^^^^^^ ^^^- default template-argument
// \ type template-parameter
// template-head
template<int i = 0>
// ^^^^^ ^- default template-argument
// \ non-type template-parameter
默认模板参数不是函数模板类型的一部分
默认模板参数不是函数模板类型的一部分,这意味着您不能使用以下方法:
// BAD: trying to define to SFINAE-mutually exclusive overloads.
template<typename T, typename = std::enable_if_t<some_predicate_v<T>>>
void f(T) {}
template<typename T, typename = std::enable_if_t<!some_predicate_v<T>>>
void f(T) {}
因为它们定义了相同的功能;参见
了解详情。
...而不同类型的非类型模板参数可以作为替代
因此,当您不对其他相同的函数进行重载时,通常会使用上述方法,而当您需要区分重载时,会使用其他方法。
// Variation A.
template<typename T,
// non-type template parameter of type void*,
// defaulted to nullptr
std::enable_if_t<some_predicate_v<T>>* = nullptr>
void f(T) {}
// OK: not the same function.
template<typename T,
std::enable_if_t<!some_predicate_v<T>>* = nullptr>
void f(T) {}
// Variation B.
template<typename T,
// non-type template parameter of type bool,
// defaulted to true or false
std::enable_if_t<some_predicate_v<T>, bool> = true>
void f(T) {}
// OK: not the same function.
template<typename T,
std::enable_if_t<!some_predicate_v<T>, bool> = true>
void f(T) {}
// Variation C.
template<typename T,
// non-type template parameter of type int,
// defaulted to 0
std::enable_if_t<some_predicate_v<T>, int> = 0>
void f(T) {}
// OK not the same function.
template<typename T,
std::enable_if_t<!some_predicate_v<T>, int> = 0>
void f(T) {}
// Variation D (uncommon/noisy).
template<typename T,
// non-type template parameter of type std::nullptr_t,
// defaulted to nullptr
std::enable_if_t<some_predicate_v<T>, std::nullptr_t> = nullptr>
void f(T) {}
// OK: not the same function.
template<typename T,
std::enable_if_t<!some_predicate_v<T>, std::nullptr_t> = nullptr>
void f(T) {}
请注意,对于变体 A,我们利用 std::enable_if
(通过 _t
别名模板别名)的第二个模板参数默认为 void
。
设置
我昨天问了一个关于模板方法重载和使用类型特征解决问题的问题。我收到了一些很好的答案,他们引导我找到了解决方案。这个解决方案让我阅读了更多书。
我登陆了 Fluent CPP 的一个页面 -- https://www.fluentcpp.com/2018/05/18/make-sfinae-pretty-2-hidden-beauty-sfinae/ 这很有趣,然后我听了 Boccara 先生提到的 Stephen Dewhurst 演讲。这一切都令人着迷。
我现在想多了解一点。在昨天的回答中,我得到了这个解决方案:
template< class Function, class... Args,
std::enable_if_t<std::is_invocable_v<Function, Args...>, std::nullptr_t> = nullptr>
explicit MyClass( const std::string & theName, Function&& f, Args&&... args )
: name(theName)
{
runner(f, args...);
}
替代答案
在阅读了 CPP Fluent post 并观看了演讲后,我得出了这个最终解决方案:
template< class Function, class... Args>
using IsInvocable = std::enable_if_t < std::is_invocable_v<Function, Args...> >;
template< class Function, class... Args, typename = IsInvocable<Function, Args...> >
explicit ThreadHandle( const std::string & name, Function && f, Args &&... args ) {
startWithName(name, f, args...);
}
第一位只是将一些语法移动到一个通用的包含文件中,但总的来说,这更简单。我认为这很干净,几乎不需要解释,即使对于不熟悉使用类型特征的人也是如此。
问题
我想知道的是这个。我收到的所有三个答案都使用了更复杂的 enable_if_t
形式,如下所示:
std::enable_if_t<std::is_invocable_v<Function, Args...>, std::nullptr_t> = nullptr>
而且我不确定如果我可以这样做,他们为什么会这样做:
std::enable_if_t< std::is_invocable_v < Function, Args... > >
有影响吗?还是这仅仅是 C++11 更复杂的问题,而现在 C++14 和 17 允许更简单的形式?也许回复的人只是通过向我展示完整的表格来帮助我。
更让我困惑的是,其中一个答案是这样的:
std::enable_if_t<!std::is_convertible_v<Function, std::string>, bool> = true>
另一个人这样做了:
std::enable_if_t<std::is_invocable_v<Function, Args...>, int> = 0>
我也不太明白这些含义。
任何帮助克服障碍都会很棒。我想会有一些情况我会想要更复杂的版本,所以更好地理解它会很好。
词汇
// template-head
template<typename T = T{}>
// ^^^^^^^^^^ ^^^- default template-argument
// \ type template-parameter
// template-head
template<int i = 0>
// ^^^^^ ^- default template-argument
// \ non-type template-parameter
默认模板参数不是函数模板类型的一部分
默认模板参数不是函数模板类型的一部分,这意味着您不能使用以下方法:
// BAD: trying to define to SFINAE-mutually exclusive overloads.
template<typename T, typename = std::enable_if_t<some_predicate_v<T>>>
void f(T) {}
template<typename T, typename = std::enable_if_t<!some_predicate_v<T>>>
void f(T) {}
因为它们定义了相同的功能;参见
了解详情。
...而不同类型的非类型模板参数可以作为替代
因此,当您不对其他相同的函数进行重载时,通常会使用上述方法,而当您需要区分重载时,会使用其他方法。
// Variation A.
template<typename T,
// non-type template parameter of type void*,
// defaulted to nullptr
std::enable_if_t<some_predicate_v<T>>* = nullptr>
void f(T) {}
// OK: not the same function.
template<typename T,
std::enable_if_t<!some_predicate_v<T>>* = nullptr>
void f(T) {}
// Variation B.
template<typename T,
// non-type template parameter of type bool,
// defaulted to true or false
std::enable_if_t<some_predicate_v<T>, bool> = true>
void f(T) {}
// OK: not the same function.
template<typename T,
std::enable_if_t<!some_predicate_v<T>, bool> = true>
void f(T) {}
// Variation C.
template<typename T,
// non-type template parameter of type int,
// defaulted to 0
std::enable_if_t<some_predicate_v<T>, int> = 0>
void f(T) {}
// OK not the same function.
template<typename T,
std::enable_if_t<!some_predicate_v<T>, int> = 0>
void f(T) {}
// Variation D (uncommon/noisy).
template<typename T,
// non-type template parameter of type std::nullptr_t,
// defaulted to nullptr
std::enable_if_t<some_predicate_v<T>, std::nullptr_t> = nullptr>
void f(T) {}
// OK: not the same function.
template<typename T,
std::enable_if_t<!some_predicate_v<T>, std::nullptr_t> = nullptr>
void f(T) {}
请注意,对于变体 A,我们利用 std::enable_if
(通过 _t
别名模板别名)的第二个模板参数默认为 void
。