57
votes

I want to have a struct that takes an arbitrary number of lambdas and serve as a central calling point for all their call operators.

If the call operator is invoked with an argument list that doesn't match any of the lambdas given on construction, a default call operator should be called.

I thought the following code would accomplish exactly this. The call operator for every lambda is "lifted" into the Poc class via using.

template <typename ...Lambdas>
struct Poc : Lambdas...
{
    using Lambdas::operator() ...; // Lift the lambda operators into the class

    template <typename ...Ts>
    auto operator() (Ts...)
    {
        std::cout << "general call" << std::endl;
    }
};

// Deduction guide
template <typename ...Ts>
Poc(Ts... ts)->Poc<Ts...>;

int main()
{
    auto l_int = [](int) {std::cout << "int" << std::endl; };

    Poc poc{l_int};
    poc(1);//calls the default operator. why?

    return 0;
}

When I don't have the default call operator in the struct, everything works as expected (with valid argument lists). If I add it to the struct (as in the code above), the default operator gets called everytime, no matter which arguments I call it with.

In my understanding, the lambda-call-operators and the structs (default) call-operator are present in the same scope. Therefore, they should all be looked at for the overload resolution. Since the lamdba-operator is more specific than the generic default operator, it should be chosen.

Apparently that is not the case. Why is that?

I tested on Microsoft Visual C++, Clang and GCC (all in their latest versions).

Edit:

1
quite an interesting thing you have there. If you dont mind I will steal the idea (just to have some fun with it ;) - 463035818_is_not_a_number
@YSC It's not CRTP, in CRTP, derived inherits from base<derived>. - Walter
@Walter Ho that's true. I let myself carried away by that code snippet. This is funny how C++ nerds as myself can become quite ecstatic with so little :) - YSC
Consider an edit: Replace all in their latest versions with their actual versions, so people in the future have a good reference point. - studog
@studog Good point, I added the versions. Since this concept seems to attract a bit of attention, I'm thinking of writing blog post about how we actually plan to use it. - LcdDrm

1 Answers

43
votes

It's simple when you spot it: your operator is not const-qualified, while the lambda's one is (unless you define the lambda as mutable). Hence, it is a better match for your non-const instance of Poc.

Just add the missingconst:

auto operator() (Ts...) const