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

时间:2017-08-04 08:52:20

标签: c++ c++11 sfinae

我想为整数类型,字符串和其他类型定义函数。

我可以写:

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)?

3 个答案:

答案 0 :(得分:2)

由于许多原因,您的示例根本无法编译。请参阅以下代码中的正确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重载可能是on-template函数:

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

答案 1 :(得分:2)

I'm pretty sure that writing something like the line below becomes quite annoying and error-prone when you want to write up to N sfinae'd versions of foo:

std::enable_if<!std::is_integral<T>::value && !std::is_same<T, std::string>::value>::type

To avoid it, you can use the choice trick (a simple way to exploit overload resolution actually).
It follows a minimal, working example:

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

It handles nicely also the arguments that are directly forwarded to the right function once it has been picked up from the set.

The basic idea is that it tries to use all the versions of your function in the order you specified, from N to 0. This has also the advantage that you can set a priority level to a function when two of them match the template parameter T with their sfinae expressions.
The sfinae expressions enable or disable the i-th choice and you can easily define a fallback by using the Choice<0> tag (that is far easier to write than std::enable_if<!std::is_integral<T>::value && !std::is_same<T, std::string>::value>::type).
The drawback (even though I wouldn't consider it a drawback) is that it requires an extra function that simply forwards the arguments to the chain by appending them to the Choice<N> tag.

See it up and running on Coliru.

答案 2 :(得分:0)

@ Jarod42是对的。 我选择了错误的方式来使用SFINAE。 实际上,我必须在返回类型声明中使用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"; }