September 11th, 2025
intriguing1 reaction

Why can’t std::apply figure out which overload I intend to use? Only one of then will work!

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).

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

4 comments

Discussion is closed. Login to edit/delete existing comments.

Sort by :
  • GL

    Forwarding to an overload set using a template would cause other problems down the roads — a brace expression does not contribute to type argument inference (“Perfect forwarding forwards objects, not braced things that are trying to become objects” covered by Raymond on 2023-07-27).

    Enumerating all overloads in the functor (or static_cast to the correct overload, or lambda for the specific overload) wouldn’t create this problem, though.

  • LB

    There are proposals to fix this problem by turning function overload sets into functors automatically, so the original code would just work. Unfortunately it’s looking like they won’t make it until the next standard after C++26 at the earliest.

  • Matt McCutchen

    There are some further insights about this topic on this Stack Overflow thread, including a suggestion to define a macro that generates the boilerplate for the lambda solution.

    > 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.”

    I think we already have a reasonable way to express that: a constraint. A simple example:
    <code>
    I used rather than in this example because it's simpler. itself does not have a constraint and there might be a good reason...

    Read more
  • R Samuel Klatchko

    FYI, there’s a small bug in the lambda example as it’s missing an opening parentheses around args. It should be:

    void test(std::tuple t)
    {
        std::apply([](auto&&... args) {
            f(std::forward(args)...);
        }, t);
    }