具有多个条件的默认模板专业化

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"; }