Home > Software design >  Why can't I pass lambda in constructor with user-defined deduction guide?
Why can't I pass lambda in constructor with user-defined deduction guide?

Time:09-07

I'm trying to create a proxy class for a function which takes Message as parameter.

template<typename MsgType>
using SendFunctor = void(*)(MsgType&);

struct Message {};

template<typename T>
struct Test {
    Test(T t) {
        Message msg;
        t(msg);
    }
};

template<typename MsgType>
Test(SendFunctor<MsgType>) -> Test<SendFunctor<MsgType>>;

Inside main then I simply declare a variable and everything works fine both with free functions and lambda

Test test([](Message&) { std::cout << "Hello2" << std::endl; });

However, after adding another template parameter to Test and modifying the deduction guide

template<typename MsgType, typename T>
struct Test {
    Test(T t) {
        Message msg;
        t(msg);
    }
};

template<typename MsgType>
Test(SendFunctor<MsgType>) -> Test<Message, SendFunctor<MsgType>>;

// or what I really want to achieve
// template<typename MsgType>
// Test(SendFunctor<MsgType>) -> Test<MsgType, SendFunctor<MsgType>>;

I got an error class template argument deduction failed. In fact, everything works fine if I pass a free function to the constructor.

Can someone please explain to me, why lambda here breaks the entire deduction? And how can I fix this?

CodePudding user response:

it doesn't work in the first case either, you're using the default deduction guide.


when you're writing

Test test([](Message&) { std::cout << "Hello2" << std::endl; });

it's actually deduced to

Test<some_lambda_type> test([](Message&) { std::cout << "Hello2" << std::endl; });

You can write

Test test( [](Message&){}); //   : convert it to function pointer

and both case should works.

CodePudding user response:

Lambdas without captures can implicitly be converted to function pointers, so other answers are wrong in that regard.

The problem is that the compiler cannot infer a template parameter from the lambda's argument's type. Think of it that way, if you passed a generic lambda (taking auto as an argument), what should be deduced?

Your example works fine, the only problem is the template deduction. You can static_cast your lambda manually to SendFunctor<Message> and it works as expected (https://godbolt.org/z/K5eveEM3c).

CodePudding user response:

lambda expression are objects (the type of that object is temporary defined by the compiler and any lambda has a different type) for which the compiler defines an operator(). In short there's no implicit conversion from lambda type to function pointer. In these cases I prefer to use std::function which has an adequate constructor to handle lambda expressions.

So a possible solution would be replace raw function pointer with std::function and encapsulate the lambda expression in a std::function to be passed to the constructor.

#include <functional>
#include <iostream>

template<typename MsgType>
using SendFunctor = std::function<void(MsgType&)>;

template<typename MsgType>
struct Test {
    Test(SendFunctor<MsgType> t) {
        MsgType msg;
        t(msg);
    }
};

struct Message {};

template<typename MsgType>
Test(SendFunctor<MsgType>) -> Test<Message>;

int main()
{
    SendFunctor<Message> f = [](Message&) { std::cout << "Hello2" << std::endl; };
    Test test(f);
}

here's a live test

---- EDIT

Playing around a bit, I also solved the ambiguity in your deduction guide by using a struct helper to deduce the argument that takes a lambda expression, thanks to this answer which you can read for completeness.

here's the helper struct deducing the argument type of a lambda.

template<class C>
struct Get_LambdaFirstArg;

template<class C, class R, class Arg>
struct Get_LambdaFirstArg<R(C::*)(Arg&)const>{
    using type = Arg;
};
template<class C, class R, class Arg>
struct Get_LambdaFirstArg<R(C::*)(Arg&)>{
    using type = Arg;
};

Here's the new deduction guide defined to handle lambda expressions.

template<typename LambdaType>
Test(LambdaType) -> Test<typename Get_LambdaFirstArg<decltype(&LambdaType::operator())>::type>;

and here's a minimal example:

#include <functional>
#include <iostream>

template<typename MsgType>
using SendFunctor = std::function<void(MsgType&)>;

template<typename MsgType>
struct Test {
    Test(SendFunctor<MsgType> t) {
        MsgType msg;
        t(msg);
    }
};

template<class C>
struct Get_LambdaFirstArg;

template<class C, class R, class Arg>
struct Get_LambdaFirstArg<R(C::*)(Arg&)const>{
    using type = Arg;
};
template<class C, class R, class Arg>
struct Get_LambdaFirstArg<R(C::*)(Arg&)>{
    using type = Arg;
};



struct Message {};

template<typename MsgType>
Test(SendFunctor<MsgType>) -> Test<Message>;

template<typename LambdaType>
Test(LambdaType) -> Test<typename Get_LambdaFirstArg<decltype(&LambdaType::operator())>::type>;

int main()
{
    Test test([](Message&) { std::cout << "Hello2" << std::endl; });
}

and here's a live test of that example.

-- EDIT finally i better tested the answer of @apple apple and it seems I was doing something wrong. Using the to convert lambda to function pointer and solves the ambiguity in your deduction guide.

  •  Tags:  
  • c
  • Related