Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing overloaded CRTP class member method to lambda

Consider this:

template<typename T>
struct base_t
{
    auto& f(int x) { return (T&)*this; }
    auto& f(char x) { return (T&)*this; }
};

struct derived_t : base_t<derived_t>
{

};

void test()
{
    derived_t instance;

    auto lambda = [&](derived_t&(derived_t::*g)(char))
    {
        (instance.*g)('a');

        //instance.f('a');
    };

    lambda(&derived_t::f);
}

Without commenting in that particular line ( //instance.f('a'); ) , I get the following error (MSVC 2019):

error C2664: 'void test::<lambda_1>::operator ()(derived_t &(__cdecl derived_t::* )(char)) const': cannot convert argument 1 from 'overloaded-function' to 'derived_t &(__cdecl derived_t::* )(char)'

When the line isn't commented out, it compiles just fine.

Why does referencing f inside lambda magically allow the compiler to convert this overloaded function?

Furthermore, without CRTP, this doesn't happen at all.

Edit: Additionally, as pointed out by @Jarod42,

  • Being explicit on return type auto& -> T& solves the issue.

  • If you use a named function instead of a lambda, the problem disappears. So apparently the interaction of lambdas and templates is relevant.

like image 981
Hi - I love SO Avatar asked Aug 15 '20 19:08

Hi - I love SO


Video Answer


1 Answers

The template mechanism instantiates classes and functions as they are used. The same mechanism is used to evaluate the types behind the keyword auto.

In your case, the return types of your base_t<T>::f functions are auto&, and require a function call to be computed. Therefore when you comment out your only call to it (instance.f('a');) the actual signature of the function cannot be computed, and the compiler cannot tell whether it can be converted to derived_t&(derived_t::*g)(char).

Commenting out instance.f('a'); is possible if you define base_t<T>::f functions as follows:

template<typename T>
struct base_t
{
    T& f(int) { return *static_cast<T*>(this); }
    T& f(char) { return *static_cast<T*>(this); }
};

Here types are deduced at instantiation of the specialized type base_t<derived_t> instead of at the call of the f functions, so the compiler can figure out its conversion to the function type derived_t&(derived_t::*g)(char) without having to call them in your code.

like image 146
Victor Paléologue Avatar answered Oct 20 '22 16:10

Victor Paléologue