Consider the following:
void f(int, int); void f(char*, char*);
void test(std::tuple<int, int> t) { std::apply(f, t); // error }
The compiler complains that it cannot deduce the type of the first parameter.
I’m using std::apply here, but the same arguments apply to functions like std::invoke and std::bind.
From inspection, we can see that the only overload that makes sense is f(int, int) since that is the only one that accepts two integer parameters.
But the compiler doesn’t know that std::apply is going to try to invoke its first parameter with arguments provided by the second parameter. The compiler has to choose an overload based on the information it is given in the function call.¹
Although the compiler could be taught the special behavior of functions like std::apply and std::invoke and use that to guide selection of an overload, codifying this would require verbiage in the standard to give those functions special treatment in the overload resolution process.
And even if they did, you wouldn’t be able to take advantage of it in your own implementations of functions similar to std::apply and std::invoke.
template<typename Callable, typename Tuple> auto logapply(Callable&& callable, Tuple&& args) { log(“applying!”); return std::apply( std::forward<Callable>(callable), std::forward<Tuple>(args)); }
The standard would have to create some general way of expressing “When doing overload resolution, look for an overload of the callable that accepts these arguments.”
Maybe you can come up with something and propose it to the standards committee.
In the meantime, you can work around this with a lambda that perfect-forwards the arguments to the overloaded function.
void test(std::tuple<int, int> t) { std::apply([](auto&&… args) { f(std::forward<decltype(args)>args)…); }, t); }
This solves the problem because the type of the lambda is, well, the lambda. The overload resolution doesn’t happen until the lambda template is instantiated with the actual parameter types from the tuple, at which point there is now enough information to choose the desired overload.
Now, in this case, we know that the answer is int, int, so the lambda is a bit wordier than it could have been.
void test(std::tuple<int, int> t) { std::apply([](int a, int b) { f(a, b); }, t); }
However, I presented the fully general std::forward version for expository purposes.
¹ You can see this problem if we change the overloads a little:
void f(int, int); void f(char*, int);
auto test(int v) { return std::bind(f, std::placeholders::_1, v); }
At the point of the bind, you don’t know whether the result is going to be invoked with an integer or a character pointer, which means that you don’t know whether you want the first overload (that takes two integers) or the second overload (that takes a character pointer and an integer).
The post Why can’t <CODE>std::apply</CODE> figure out which overload I intend to use? Only one of then will work! appeared first on The Old New Thing.
From The Old New Thing via this RSS feed