SFINAE on functions with default parameters – free function vs operator()

  • A+
Category:Languages

I was playing around with this answer to investigate how it handles functions with default parameters. To my surprise, the results are different for free functions and operator():

template <typename F> auto func(F f) -> decltype(f(42)) {     int a = 51;     return f(51); }  template <typename F> auto func(F f) -> decltype(f(42, 42)) {     int a = 0;     int b = 10;     return f(a, b); }  int defaultFree(int a, int b = 0) {     return a; }  auto defaultLambda = [](int a, int b = 0) {     return a; };  int foo() {     return func(defaultFree);       //return func(defaultLambda); } 

Godbolt link

The func(defaultFree) version above compiles while both func templates are available. As expected, it picks the second one because default parameters are not considered part of the function signature. Indeed, removing the second func template causes a compilation error.

However, func(defaultLambda) does not compile due to ambiguity: Both func templates match. Removing either one makes this version compile.

(The same happens if you manually write a struct with a similar operator(), of course. Latest gcc, clang and MSVC all agree on it, too.)

What is the reason that the default parameter is considered inside the unevaluated SFINAE context for operator() but not for the free function?

 


When you pass the free function as an argument, it undergoes the function-to-pointer conversion. When that happens, the default argument (which is not a part of the function's type) is gone. It's now a pointer to a function taking two parameters, and as such only one SFINAE check can pass for it.

The lambda's type doesn't undergo such an adjustment. The unevaluated expression must involve overload resolution for operator(), and that finds the declaration with the default argument, which allows it be used in a call with a single argument.

When the capturesless lambda is forced to undergo a conversion to a function pointer (for instance func(+defaultLambda);, courtesy of @YSC), the ambiguity is gone, for the same reason.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: