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

Viewed 117

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?

3 Answers

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.

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

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.

Related