我正在学习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<<
接受的所有类型的解决方案。理想情况下,它不应该依赖于定义一个“污染”命名空间的帮助器类型。
答案 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保护函数,因为int
与char
的{{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; }