1

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?

Bioliquid
  • 161
  • 7
  • Your deduction guide doesn't really make sense in the second part. You're forcing `MsgType` to be `Message` anyway. What are you really trying to do? – AndyG Sep 06 '22 at 15:17

3 Answers3

3

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.

apple apple
  • 10,292
  • 2
  • 16
  • 36
-1

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

  • if an explicit cast is required, why are you saying they are implicitly convertible? – IkarusDeveloper Sep 06 '22 at 17:01
  • @IkarusDeveloper The explicit cast is required because the default template deduction guide is a better match without it. – AndyG Sep 06 '22 at 18:10
  • @IkarusDeveloper one can convert (limited) lambda to function pointer, and deduce parameter type `T` from (that specific) function pointer, but cannot directly deduce `T` from a lambda – apple apple Sep 06 '22 at 19:29
-2

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.

IkarusDeveloper
  • 369
  • 4
  • 8