C ++ 11:重载决策和SFINAE

时间:2015-08-13 09:36:28

标签: c++ c++11 template-meta-programming sfinae overload-resolution

我正在学习SFINAE,这是我第一次尝试打印“是”仅适用于您可以使用std::ostream输出的类型(暂时忘记std::operator<<(std::ostream &, T) ...):

template <typename T>
void f(const T &) { std::cout << "NO" << std::endl; }

template <typename T, int SFINAE = sizeof(static_cast<std::ostream &(std::ostream::*)(T)>(
    &std::ostream::operator<<))>
void f(const T &) { std::cout << "YES" << std::endl; }

虽然它们似乎与f(std::vector<int>())一起使用(产生“NO”),但编译器抱怨f(0)含糊不清:http://ideone.com/VljXFh

prog.cpp:16:5: error: call of overloaded 'f(int)' is ambiguous
  f(0);
     ^
prog.cpp:6:6: note: candidate: void f(const T&) [with T = int]
 void f(const T &) { std::cout << "NO" << std::endl; }
      ^
prog.cpp:10:6: note: candidate: void f(const T&) [with T = int; int SFINAE = 8]
 void f(const T &) { std::cout << "YES" << std::endl; }
      ^

如何修复我的代码? “YES”版本是否比完全通用的“NO”版本更具体?

澄清

f(0)f(0.)f(true)的所有错误都会出现相同的“模糊”错误。我正在寻找适用于std::ostream::operator<<接受的所有类型的解决方案。理想情况下,它不应该依赖于定义一个“污染”命名空间的帮助器类型。

2 个答案:

答案 0 :(得分:7)

NO版本对int仍然有效,并且在两次重载之间没有适用的部分排序,因此调用不明确。

消除歧义的一种简单方法是在函数中添加一个额外的标记参数:

template <typename T>
void f(const T &, char) { std::cout << "NO" << std::endl; }
//                ^^^^

template <typename T, int SFINAE = sizeof(static_cast<std::ostream &(std::ostream::*)(T)>(
    &std::ostream::operator<<))>
void f(const T &, int) { std::cout << "YES" << std::endl; }
//                ^^^

现在,当您调用该函数时,只需传递一个额外的0(或者编写一个辅助函数来为您执行此操作)。如果SFINAE保护函数有效,则首选SFINAE保护函数,因为intchar的{​​{1}}匹配得比0更好。请参阅this article以获得更清晰的表达歧义的方式。

或者,您可以编写一个特征来检查操作符是否对给定类型有效,然后使用std::enable_if<check<T>>std::enable_if<!check<T>>来避免消除歧义的参数。

顺便提一下,您可以使用此类SFINAE的decltype和尾随返回类型,我认为它看起来更清晰:

template <typename T>
void f(const T &, char) { std::cout << "NO" << std::endl; }

template <typename T>
auto f(const T &t, int) -> decltype(std::declval<std::ostream&>() << t, void())
{ std::cout << "YES" << std::endl; }

当我们获得C ++ Concepts时,您将能够执行类似的操作(这适用于启用概念的GCC):

template <typename T>
concept bool Outputtable = requires (T t, std::ostream o) { o << t; };

template <typename T>
void f(const T &) { std::cout << "NO" << std::endl; }

template <Outputtable T>
void f(const T &) { std::cout << "YES" << std::endl; }

答案 1 :(得分:3)

从C ++ 17开始,可以使用std::void_t来描述它:

template<typename, typename = std::void_t<>>
struct enables_ostream_output : std::false_type {};

template<typename Type>
struct enables_ostream_output<
    Type,
    std::void_t<decltype(std::declval<std::ostream>() << std::declval<Type>())>
> : std::true_type {};

与经典std::enable_if

结合使用
template <typename Type>
typename std::enable_if<!enables_ostream_output<Type>::value>::type
f(const Type &) { std::cout << "NO" << std::endl; }

template <typename Type>
typename std::enable_if<enables_ostream_output<Type>::value>::type
f(const Type &) { std::cout << "YES" << std::endl; }

根据@TartanLlama的建议,另一种选择是将std::(experimental::)is_detected用作:

template<typename Type>
using ostream_output_t = decltype(std::declval<std::ostream>() << std::declval<Type>());

然后:

template <typename Type>
typename std::enable_if<!std::is_detected<ostream_output_t, Type>::value>::type
f(const Type &) { std::cout << "NO" << std::endl; }

template <typename Type>
typename std::enable_if<std::is_detected<ostream_output_t, Type>::value>::type
f(const Type &) { std::cout << "YES" << std::endl; }