仅在不存在时才启用的 C++ 函数

C++ function which is only enabled if it doesn't exist

我想我刚刚提出了一个 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
}

据我所知,所有这些似乎都与声明顺序无关。

我的问题是:这里究竟发生了什么?这是标准定义的行为,我尝试过的所有编译器都以相同方式处理的未定义条件,还是我尝试过的所有编译器中巧合存在的错误?

我怀疑这一切都被 [temp.inst]:

简单地涵盖了

The result of an infinite recursion in instantiation is undefined.

无论您如何定义has_f,它都涉及无限递归。 has_f<Bar>涉及实例化f(Bar )涉及实例化has_f<Bar>涉及实例化...

事实上,一种定义 has_f 的方法在某些情况下有效但在其他情况下无效,而另一种方法肯定无效,这只是未定义行为的结果。未定义的行为是未定义的。