Search code examples
c++overloadingc++20c++-conceptsoverload-resolution

Passing a concept-constrained function overload


The following code fails to compile (Godbolt link):

#include <concepts>

template <class Fn>
decltype(auto) g(Fn&& fn) { return fn(); }

template <typename T>
requires(std::integral<T>) int f() { return 0; }

template <typename T>
int f() { return 1; }

int main() {
  f<int>();
  f<void>();
  g(f<int>); // error: invalid initialization of non-const reference of type 'int (&)()'
             // from an rvalue of type '<unresolved overloaded function type>'
  g(f<void>);
}

It seems unexpected to me that the overload resolution succeeds when calling f<int>() (selecting the constrained version as a better match than the unconstrained version) but fails when passing f<int> as an argument.

Note that changing the unconstrained version to a disjoint constraint does make it compile (Godbolt link):

#include <concepts>

template <class Fn>
decltype(auto) g(Fn&& fn) { return fn(); }

template <typename T>
requires(std::integral<T>) int f() { return 0; }

template <typename T>
requires(!std::integral<T>) int f() { return 1; }

int main() {
  f<int>();
  f<void>();
  g(f<int>);
  g(f<void>);
}

So is the compiler behavior correct? And if so, is this an inconsistency in the standard, or is it intended to work this way?


Solution

  • It seems that neither GCC nor Clang has fully implemented the rules for forming pointers to constrained functions: [over.over]/5 definitely considers constraint ordering in choosing an overload. There were some late changes to these, although they’re just as relevant to the disjoint-constraints case as to the unconstrained case.