11

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?

jcai
  • 3,448
  • 3
  • 21
  • 36
  • I'm not sure if the compilers are correct or not, but another workaround could be to add a cast: `g(static_cast(f));` [example](https://godbolt.org/z/b44d9v8j9). I'm not sure why that seems to help though. Edit: Oups, it only worked in clang++, not g++. – Ted Lyngmo Apr 05 '22 at 18:12
  • @TedLyngmo That doesn't seem to work, the ambiguity is at the point of conversion and not related to parameter passing. `int (*x)() = &f;` fails with the same error (on g++, as OP's godbolt link uses). – Ben Voigt Apr 05 '22 at 18:14
  • @BenVoigt Yes, I noticed when I changed from clang++ to g++. Odd that clang++ accepts that cast when it doesn't accept plain `g(f)` – Ted Lyngmo Apr 05 '22 at 18:15

1 Answers1

5

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.

Davis Herring
  • 36,443
  • 4
  • 48
  • 76