function overloading with std::function and generic lambdas: std::string preferred over int

208 views Asked by At

When trying to compiling this, suprisingly, it gives an error because the auto parameter of the lambda function has been resolved to std::string, and the compiler doesn't know how to convert std::string to int or Widget when calling test.

But, I wonder why the compiler has choosen the second invok function instead of the first one, when the first one would succeed:

#include <string>
#include <functional>

struct Widget {};

bool test(int );
bool test(Widget );

void invok(std::function<bool(int)> );         // #1
void invok(std::function<bool(std::string)> ); // #2

int main()
{
    // error: unresolved overloaded function type
    // invok(test);             

    // still error: no known conversion from std::string to
    // int or Widget
    invok([](auto&& x) {       
       return test(std::forward<decltype(x)>(x));
    });

}

That example has been copied from a C++ proposal.

2

There are 2 answers

0
T.C. On

The compiler didn't choose #2. It's trying to decide if it can choose #2.

To do that, it asks "can this generic lambda be converted to std::function<bool(std::string)>"?

std::function's converting constructor says "only if it's callable with a std::string rvalue and the result type is convertible to bool".

Compiler tries that, deduce auto as std::string, substitute into the signature of the function call operator...success! Oops, the return type is auto, and it needs an actual type to answer the "is convertible" question. So it instantiates the body of the function call operator template to figure out the return type.

Ouch. The body isn't valid for std::string after all. Hard error and explosions follow.

2
Yakk - Adam Nevraumont On

Your overload remains ambiguous (sort of), but along the way to determining that your code runs into a hard error when trying out the two possibilities (passing an int or a string).

To see the ambiguity, add a ->bool to your lambda so it doesn't have to compile the body to determine the return value.

The body of a lambda is not in an area where a subsitution failure results in not an error. Instead you get a hard error there.

The easy fix is to make your lambda take an int explicitly.

If you want a generic solution:

#define RETURNS(...) \
  noexcept(noexcept(__VA_ARGS__)) \
  -> decltype(__VA_ARGS__) \
  { return __VA_ARGS__; }

#define OVERLOADS_OF(...) \
  [](auto&&...args) \
  RETURNS( __VA_ARGS__( decltype(args)(args)... ) )

and then

invok(OVERLOADS_OF(test));

does (at least closer to) the right thing.

This macro moves the failure from the body of the lambda, to a trailing return type. And the failure (that string cannot be passed to test) now occurs in a context where substitution failure causes not an error (SFINAE). So everything works.

The exact rules for what is SFINAE friendly and what is not require reading the standard. However, the rule of thumb is that works well is that compilers don't have to treat errors in bodies of functions as substitution errors, and that accessing the contents of an undefined class is a hard error. The first because it seemed a reasonable place to draw a line and make it easier for compiler writers; the second because the alternative is insanity or ODR bug bait.

In practice, the standards SFINAE rules are more arcane, and last I checked in C++14 there was an omission of SFINAE being required during template class partial specialization: every compiler supported it. But maybe I misread it. In any case, the rule of thumb I use seems just as useful as the standard text. Neither are going to be perfect.