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.