如何约束模板?std::enable_if的六种不同用法



我正在尝试理解使用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);

这两个版本有什么不同?什么时候应该使用?


第二个问题

由于我的一个错误,我发现如果缺少一个typename,版本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++标准(c++ 20之前)的兼容性,并且您不需要引用模板类型,并且约束仅涉及单个模板参数,则首选最少的样板选项:

// #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不适用于没有返回类型的构造函数。但是,在#6中,您可以方便地命名函数参数。

选项#5是最常用的SFINAE选项。如果你一定要看的话,我更喜欢它。

关于问题#2,对typename的放宽是在c++ 20中出现的,在这里和这里都有描述

相关内容

最新更新