std::enable_if<> 在条件编译模板中的六种不同用法
Six different usages of std::enable_if<> in conditionally compiled templates
我正在尝试了解使用 std::enable_if<>
.
的模板函数的两个不同版本
版本 1:
template<class T, typename std::enable_if<std::is_convertible<T, std::string_view>::value, T>::type* = nullptr>
void foo(const T& msg);
版本 2:
template<class T, typename = typename std::enable_if<std::is_convertible<T, std::string_view>::value>::type>
void foo(const T& msg);
如果我没理解错的话,如果满足条件的话应该转换成:
// Version 1
template<class T, T* = nullptr>
void foo(const T& msg);
// Version 2
template<class T, typename = void>
void foo(const T& msg);
两个版本同样可以被调用:
std::string s = "Test";
foo(s);
这两个版本有什么区别?什么时候用?
第二个问题
由于我的错误,我发现如果缺少一个类型名,版本 2 也可以编译:
//Correct Version 2 like above:
template<class T, typename = typename std::enable_if<std::is_convertible<T, std::string_view>::value>::type>
void foo(const T& msg);
// My "faulty" version, also works. Is this correct too?
template<class T, typename = std::enable_if<std::is_convertible<T, std::string_view>::value>::type>
void foo(const T& msg);
第二个(错误的)版本是否也正确?我认为 std::enable_if<>
前面确实需要一个 typename
。
如何约束模板?
如果你不局限于与旧的C++标准的兼容性,并且你不需要引用模板类型,并且约束只涉及单个模板参数,则首选最少的样板选项:
// #1
void foo(const std::convertible_to<std::string_view> auto& msg);
否则,更喜欢稍微冗长的形式:
// #2
template <typename T>
requires std::convertible_to<T, std::string_view>
void foo(const T& msg);
如果约束涉及多个模板参数,形式 #2 为模板类型命名并继续起作用。它仍然不能直接适用于较旧的 C++,但约束的位置与较旧的 C++ enable_if
用法兼容:
// #2, compatible version
// C++11
#define TEMPLATE(...) template <__VA_ARGS__
#define REQUIRES(C) , typename std::enable_if<(C), int>::type = 0>
#define CONVERTIBLE_TO(From, To) std::is_convertible<From, To>::value
// C++20
#define TEMPLATE(...) template <__VA_ARGS__>
#define REQUIRES(C) requires (C)
#define CONVERTIBLE_TO(From, To) std::convertible_to<From, To>
TEMPLATE(typename T)
REQUIRES(CONVERTIBLE_TO(T, std::string_view))
void foo(const T& msg);
以下选项也可用,但我会坚持#1 或#2:
// #3
template <std::convertible_to<std::string_view> T>
void foo(const T& msg);
// #4
template <typename T>
void foo(const T& msg) requires std::convertible_to<T, std::string_view>;
关于enable_if
,有以下三种选择:
// #5, non-type template parameter with default value ("version 1")
template <typename T, typename std::enable_if_t<std::is_convertible_v<T, std::string_view>, int> = 0>
void foo(const T& msg);
// #6, enable_if in the return type
template<typename T>
auto foo(const T& msg) -> typename std::enable_if_t<std::is_convertible_v<T, std::string_view>>;
// #7, defaulted template parameter ("version 2")
template<class T, typename = typename std::enable_if_t<std::is_convertible_v<T, std::string_view>>>
void foo(const T& msg);
选项 #7(“版本 2”)很少被推荐,因为默认模板参数不参与函数签名。所以,一旦你有两个重载,它就是模棱两可的。并且过载集会增长。
选项 #6 不适用于缺少 return 类型的构造函数。但是,在#6 中,您可以命名函数参数,这很方便。
选项 #5 是最通用的 SFINAE 选项。如果你必须SFINAE,请选择它。
我正在尝试了解使用 std::enable_if<>
.
版本 1:
template<class T, typename std::enable_if<std::is_convertible<T, std::string_view>::value, T>::type* = nullptr>
void foo(const T& msg);
版本 2:
template<class T, typename = typename std::enable_if<std::is_convertible<T, std::string_view>::value>::type>
void foo(const T& msg);
如果我没理解错的话,如果满足条件的话应该转换成:
// Version 1
template<class T, T* = nullptr>
void foo(const T& msg);
// Version 2
template<class T, typename = void>
void foo(const T& msg);
两个版本同样可以被调用:
std::string s = "Test";
foo(s);
这两个版本有什么区别?什么时候用?
第二个问题
由于我的错误,我发现如果缺少一个类型名,版本 2 也可以编译:
//Correct Version 2 like above:
template<class T, typename = typename std::enable_if<std::is_convertible<T, std::string_view>::value>::type>
void foo(const T& msg);
// My "faulty" version, also works. Is this correct too?
template<class T, typename = std::enable_if<std::is_convertible<T, std::string_view>::value>::type>
void foo(const T& msg);
第二个(错误的)版本是否也正确?我认为 std::enable_if<>
前面确实需要一个 typename
。
如何约束模板?
如果你不局限于与旧的C++标准的兼容性,并且你不需要引用模板类型,并且约束只涉及单个模板参数,则首选最少的样板选项:
// #1
void foo(const std::convertible_to<std::string_view> auto& msg);
否则,更喜欢稍微冗长的形式:
// #2
template <typename T>
requires std::convertible_to<T, std::string_view>
void foo(const T& msg);
如果约束涉及多个模板参数,形式 #2 为模板类型命名并继续起作用。它仍然不能直接适用于较旧的 C++,但约束的位置与较旧的 C++ enable_if
用法兼容:
// #2, compatible version
// C++11
#define TEMPLATE(...) template <__VA_ARGS__
#define REQUIRES(C) , typename std::enable_if<(C), int>::type = 0>
#define CONVERTIBLE_TO(From, To) std::is_convertible<From, To>::value
// C++20
#define TEMPLATE(...) template <__VA_ARGS__>
#define REQUIRES(C) requires (C)
#define CONVERTIBLE_TO(From, To) std::convertible_to<From, To>
TEMPLATE(typename T)
REQUIRES(CONVERTIBLE_TO(T, std::string_view))
void foo(const T& msg);
以下选项也可用,但我会坚持#1 或#2:
// #3
template <std::convertible_to<std::string_view> T>
void foo(const T& msg);
// #4
template <typename T>
void foo(const T& msg) requires std::convertible_to<T, std::string_view>;
关于enable_if
,有以下三种选择:
// #5, non-type template parameter with default value ("version 1")
template <typename T, typename std::enable_if_t<std::is_convertible_v<T, std::string_view>, int> = 0>
void foo(const T& msg);
// #6, enable_if in the return type
template<typename T>
auto foo(const T& msg) -> typename std::enable_if_t<std::is_convertible_v<T, std::string_view>>;
// #7, defaulted template parameter ("version 2")
template<class T, typename = typename std::enable_if_t<std::is_convertible_v<T, std::string_view>>>
void foo(const T& msg);
选项 #7(“版本 2”)很少被推荐,因为默认模板参数不参与函数签名。所以,一旦你有两个重载,它就是模棱两可的。并且过载集会增长。
选项 #6 不适用于缺少 return 类型的构造函数。但是,在#6 中,您可以命名函数参数,这很方便。
选项 #5 是最通用的 SFINAE 选项。如果你必须SFINAE,请选择它。