我想我刚刚做了一个C ++悖论......
#include <type_traits>
#include <utility>
// If has_f trait is defined in this way, compilation breaks because of infinite recursion in template substitution
/*
template< typename T, typename Enable=void >
struct has_f : std::false_type { };
template< typename T >
struct has_f<T, decltype(f(std::declval<T>()))> : std::true_type { };
*/
// Defining has_f like this works on MSVC, gcc and CLang
namespace has_f_impl {
struct no{ };
template< typename T >
no check(...);
template< typename T >
decltype(f(std::declval<T>())) check(void const*);
template< typename T >
struct has_f : std::integral_constant<bool, !std::is_same<no, decltype(check<T>(nullptr))>::value> { };
}
using has_f_impl::has_f;
struct Foo { };
struct Bar { };
template< typename T, std::enable_if_t<std::is_same<Foo, T>::value, int> = 0 >
void f(T const&);
template< typename T, std::enable_if_t<!has_f<T const&>::value, int> = 1 >
void f(T const&);
int main() {
f(Foo()); // Calls f<Foo,0>()
f(Bar()); // Calls f<Bar,1>()
f(Foo()); // Calls f<Foo,0>()
f(Bar()); // Calls f<Bar,1>()
}
以上代码令人惊讶地工作,并且以非常聪明的方式,只有在没有其他选项的情况下才使用通用f
。
此外,这可能是因为ODR,会发生以下情况
// Includes, has_f, Foo, Bar and f as above
template< typename T, std::enable_if_t<has_f<T const&>::value>* = nullptr >
void g(T const&);
int main() {
f(Foo()); // Calls f<Foo,0>()
f(Bar()); // Calls f<Bar,1>()
f(Foo()); // Calls f<Foo,0>()
f(Bar()); // Calls f<Bar,1>()
g(Foo());
//g(Bar()); //No such function
}
据我所知,所有这些似乎都与声明顺序无关。
我的问题是:这里到底发生了什么?这是一个标准定义的行为,一个未定义的条件,我试图以相同的方式处理所有编译器,或者在我尝试的所有编译器中巧妙存在的错误?
答案 0 :(得分:2)
我怀疑这只是[temp.inst]所涵盖的所有内容:
实例化中无限递归的结果是 未定义。
无论您定义has_f
的方式如何,都涉及无限递归。 has_f<Bar>
涉及f(Bar )
的实例化,涉及has_f<Bar>
的实例化,涉及实例化......
事实上,一种定义has_f
的方法在某些情况下有效但在其他情况下无效,而另一种方式肯定不起作用,这只是未定义行为的结果。未定义的行为未定义。