具有多个条件的默认模板专业化
Default template specialization with multiple conditions
我想为整数类型、字符串和其他类型定义函数。
我会写:
template<typename T, typename = std::enable_if<std::is_integral<T>::value>::type>
void foo();
template<typename T, typename = std::enable_if<std::is_same<std::string>::value>::type>
void foo();
但是我如何定义将在其他情况下调用的函数(如果 T
不是整数类型而不是 std::string
)?
出于多种原因,您的示例根本无法编译。在下面的代码中查看正确的 SFINAE。这只是许多可能的方法之一。
你可以同时否定所有的特殊条件。例如:
template<typename T, std::enable_if_t<std::is_integral<T>::value>* = nullptr>
void foo() { std::cout << "Integral\n"; }
template<typename T, std::enable_if_t<std::is_same<T, std::string>::value>* = nullptr>
void foo() { std::cout << "Str\n"; }
template<typename T, std::enable_if_t<!std::is_integral<T>::value && !std::is_same<T, std::string>::value>* = nullptr>
void foo() { std::cout << "Something else\n"; }
int main(void)
{
foo<int>();
foo<std::string>();
foo<float>();
return 0;
}
打印:
Integral
Str
Something else
请注意,如果您的函数采用依赖于模板的参数,您可能会获得自动重载决议。 SFINAE 在这种情况下看起来会有点不同:
template<typename T>
typename std::enable_if<std::is_integral<T>::value>::type foo(const T&) { std::cout << "Integral\n"; }
template<typename T>
typename std::enable_if<std::is_same<T, std::string>::value>::type foo(const T&) { std::cout << "Str\n"; }
template<typename T>
typename std::enable_if<!std::is_integral<T>::value && !std::is_same<T, std::string>::value>::type
foo(const T&) { std::cout << "Something else\n"; }
用法:
foo(1);
foo(std::string("fdsf"));
foo(1.1f);
最后,在最后一种情况下 std::string
重载可能是模板函数:
template<typename T>
typename std::enable_if<std::is_integral<T>::value>::type foo(const T&) { std::cout << "Integral\n"; }
template<typename T>
typename std::enable_if<!std::is_integral<T>::value && !std::is_same<T, std::string>::value>::type
foo(const T&) { std::cout << "Something else\n"; }
void foo(const std::string&) { std::cout << "Str\n"; }
我敢肯定,当您想编写多达 N 个 sfinae 版本的 foo
:
时,编写类似下面这行的内容会变得非常烦人且容易出错
std::enable_if<!std::is_integral<T>::value && !std::is_same<T, std::string>::value>::type
要避免它,您可以使用选择技巧(一种实际利用重载解析的简单方法)。
它遵循一个最小的工作示例:
#include <iostream>
#include <utility>
#include <string>
template<int N> struct Choice: Choice<N-1> {};
template<> struct Choice<0> {};
template<typename T, typename... Args>
std::enable_if_t<std::is_integral<T>::value>
bar(Choice<2>, Args&&...) { std::cout << "integral" << std::endl; }
template<typename T, typename... Args>
std::enable_if_t<std::is_same<T, std::string>::value>
bar(Choice<1>, Args&&...) { std::cout << "string" << std::endl; }
template<typename T, typename... Args>
void bar(Choice<0>, Args&&...) { std::cout << "whatever" << std::endl; }
template<typename T, typename... Args>
void foo(Args&&... args) { bar<T>(Choice<100>{}, std::forward<Args>(args)...); }
int main() {
foo<bool>("foo");
foo<std::string>(42);
foo<void>(.0, "bar");
}
它还可以很好地处理从集合中提取后直接转发给正确函数的参数。
基本思想是它尝试按照您指定的顺序使用函数的所有 版本 ,从 N 到 0。这也有一个优点,您可以设置当其中两个将模板参数 T
与其 sfinae 表达式匹配时,函数的优先级。
sfinae 表达式启用或禁用第 i 个选择,您可以使用 Choice<0>
标记轻松定义 fallback(这比 std::enable_if<!std::is_integral<T>::value && !std::is_same<T, std::string>::value>::type
更容易编写).
缺点(即使我不认为这是缺点)是它需要一个额外的函数,通过将参数附加到 Choice<N>
标签来简单地将参数转发到链中。
在 Coliru 上查看并 运行。
@Jarod42 是对的。
我选择了错误的方式来使用 SFINAE。
实际上,我必须在 return 类型声明中使用 std::enable_if。为了定义 'default' 函数(对于所有其他类型),我只需要定义具有相同名称并使用 ...
作为输入参数的函数。
template<typename T>
std::enable_if_t<std::is_integral<T>::value>
foo() { std::cout << "integral"; }
template<typename T>
std::enable_if_t<std::is_same<T, std::string>::value>
foo() { std::cout << "string"; }
template<typename T>
void foo(...) { std::cout << "other"; }
我想为整数类型、字符串和其他类型定义函数。
我会写:
template<typename T, typename = std::enable_if<std::is_integral<T>::value>::type>
void foo();
template<typename T, typename = std::enable_if<std::is_same<std::string>::value>::type>
void foo();
但是我如何定义将在其他情况下调用的函数(如果 T
不是整数类型而不是 std::string
)?
出于多种原因,您的示例根本无法编译。在下面的代码中查看正确的 SFINAE。这只是许多可能的方法之一。
你可以同时否定所有的特殊条件。例如:
template<typename T, std::enable_if_t<std::is_integral<T>::value>* = nullptr>
void foo() { std::cout << "Integral\n"; }
template<typename T, std::enable_if_t<std::is_same<T, std::string>::value>* = nullptr>
void foo() { std::cout << "Str\n"; }
template<typename T, std::enable_if_t<!std::is_integral<T>::value && !std::is_same<T, std::string>::value>* = nullptr>
void foo() { std::cout << "Something else\n"; }
int main(void)
{
foo<int>();
foo<std::string>();
foo<float>();
return 0;
}
打印:
Integral
Str
Something else
请注意,如果您的函数采用依赖于模板的参数,您可能会获得自动重载决议。 SFINAE 在这种情况下看起来会有点不同:
template<typename T>
typename std::enable_if<std::is_integral<T>::value>::type foo(const T&) { std::cout << "Integral\n"; }
template<typename T>
typename std::enable_if<std::is_same<T, std::string>::value>::type foo(const T&) { std::cout << "Str\n"; }
template<typename T>
typename std::enable_if<!std::is_integral<T>::value && !std::is_same<T, std::string>::value>::type
foo(const T&) { std::cout << "Something else\n"; }
用法:
foo(1);
foo(std::string("fdsf"));
foo(1.1f);
最后,在最后一种情况下 std::string
重载可能是模板函数:
template<typename T>
typename std::enable_if<std::is_integral<T>::value>::type foo(const T&) { std::cout << "Integral\n"; }
template<typename T>
typename std::enable_if<!std::is_integral<T>::value && !std::is_same<T, std::string>::value>::type
foo(const T&) { std::cout << "Something else\n"; }
void foo(const std::string&) { std::cout << "Str\n"; }
我敢肯定,当您想编写多达 N 个 sfinae 版本的 foo
:
std::enable_if<!std::is_integral<T>::value && !std::is_same<T, std::string>::value>::type
要避免它,您可以使用选择技巧(一种实际利用重载解析的简单方法)。
它遵循一个最小的工作示例:
#include <iostream>
#include <utility>
#include <string>
template<int N> struct Choice: Choice<N-1> {};
template<> struct Choice<0> {};
template<typename T, typename... Args>
std::enable_if_t<std::is_integral<T>::value>
bar(Choice<2>, Args&&...) { std::cout << "integral" << std::endl; }
template<typename T, typename... Args>
std::enable_if_t<std::is_same<T, std::string>::value>
bar(Choice<1>, Args&&...) { std::cout << "string" << std::endl; }
template<typename T, typename... Args>
void bar(Choice<0>, Args&&...) { std::cout << "whatever" << std::endl; }
template<typename T, typename... Args>
void foo(Args&&... args) { bar<T>(Choice<100>{}, std::forward<Args>(args)...); }
int main() {
foo<bool>("foo");
foo<std::string>(42);
foo<void>(.0, "bar");
}
它还可以很好地处理从集合中提取后直接转发给正确函数的参数。
基本思想是它尝试按照您指定的顺序使用函数的所有 版本 ,从 N 到 0。这也有一个优点,您可以设置当其中两个将模板参数 T
与其 sfinae 表达式匹配时,函数的优先级。
sfinae 表达式启用或禁用第 i 个选择,您可以使用 Choice<0>
标记轻松定义 fallback(这比 std::enable_if<!std::is_integral<T>::value && !std::is_same<T, std::string>::value>::type
更容易编写).
缺点(即使我不认为这是缺点)是它需要一个额外的函数,通过将参数附加到 Choice<N>
标签来简单地将参数转发到链中。
在 Coliru 上查看并 运行。
@Jarod42 是对的。
我选择了错误的方式来使用 SFINAE。
实际上,我必须在 return 类型声明中使用 std::enable_if。为了定义 'default' 函数(对于所有其他类型),我只需要定义具有相同名称并使用 ...
作为输入参数的函数。
template<typename T>
std::enable_if_t<std::is_integral<T>::value>
foo() { std::cout << "integral"; }
template<typename T>
std::enable_if_t<std::is_same<T, std::string>::value>
foo() { std::cout << "string"; }
template<typename T>
void foo(...) { std::cout << "other"; }