Reputation: 189
I'm writing a message receiving library and want to write a simple lambda to call when a message is received on a given endpoint.
When I try a simple lambda, it doesn't match the std::function
template because the lambda is not the exact type, which makes sense.
#include <iostream>
#include <unistd.h>
#include <functional>
#include <memory>
const std::string endpoint1 = "ipc:///tmp/endpoint1.ipc";
const std::string endpoint2 = "ipc:///tmp/endpoint2.ipc";
class ISubscriber {
public:
virtual ~ISubscriber() {};
};
template <typename T>
class Subscriber : public ISubscriber
{
public:
Subscriber(const std::string & endpoint, std::function<void (const T&)> callback);
};
class Context
{
public:
template <typename T>
void addSubscriberListener(const std::string & endpoint, std::function<void(const T &)> callback)
{
// add to subscribers list
}
private:
std::vector<std::unique_ptr<ISubscriber>> _subscribers;
// All the other goo to make messaging work
};
class Type1 {};
class Type2 {};
void test()
{
Context context;
#if 1 // Desired syntax
context.addSubscriberListener(endpoint1, [] (const Type1 & t) {
});
context.addSubscriberListener(endpoint2, [] (const Type2 & t) {
});
#else // Undesired syntax
context.addSubscriberListener(endpoint1, std::function<void(const Type1 &)>([] (const Type1 & t) {
}));
context.addSubscriberListener(endpoint2, std::function<void(const Type2 &)>([] (const Type2 & t) {
}));
#endif
}
The desired syntax gives me
Test.cpp: In function ‘void test()’:
Test.cpp:43:10: error: no matching function for call to ‘Context::addSubscriberListener(const string&, test()::<lambda(const Type1&)>)’
});
^
Test.cpp:25:11: note: candidate: ‘template<class T> void Context::addSubscriberListener(const string&, std::function<void(const T&)>)’
void addSubscriberListener(const std::string & endpoint, std::function<void(const T &)> callback)
^~~~~~~~~~~~~~~~~~~~~
Test.cpp:25:11: note: template argument deduction/substitution failed:
Test.cpp:43:10: note: ‘test()::<lambda(const Type1&)>’ is not derived from ‘std::function<void(const T&)>’
});
^
Test.cpp:45:7: error: no matching function for call to ‘Context::addSubscriberListener(const string&, test()::<lambda(const Type2&)>)’
});
^
Test.cpp:25:11: note: candidate: ‘template<class T> void Context::addSubscriberListener(const string&, std::function<void(const T&)>)’
void addSubscriberListener(const std::string & endpoint, std::function<void(const T &)> callback)
^~~~~~~~~~~~~~~~~~~~~
Test.cpp:25:11: note: template argument deduction/substitution failed:
Test.cpp:45:7: note: ‘test()::<lambda(const Type2&)>’ is not derived from ‘std::function<void(const T&)>’
});
It's not obvious to me if I need extra plumbing, or what that plumbing should be to make it work.
What are my options to get to the desired syntax?
Upvotes: 2
Views: 141
Reputation: 66230
But you really need that addSubscriberListener()
receive a std::function
?
What about simply accept a generic type F
(for "functional") ?
template <typename F>
void addSubscriberListener (const std::string & endpoint, F callback)
{
// add to subscribers list
}
This way you can go around your chicken-and-egg problem you have: the lambda can be converted to a std::function
but isn't a std::function
so the compiler must known the T
type to convert the lambda to a std::function
and deduct from it the T
type.
Anyway... if you need to maintain the std::function
... not exactly the desired syntax but... you could explicit the T
type
// --------------------------VVVVVVV
context.addSubscriberListener<Type1>(endpoint1, [] (const Type1 & t) {
});
context.addSubscriberListener<Type2>(endpoint2, [] (const Type2 & t) {
});
// --------------------------^^^^^^^
It's another way to break the chicken-and-egg problem: you explicit the type T
, so the compiler know that the lambda has to used to initialize a std::function<void(Type1 const &)>
(fist call) and a std::function<void(Type2 const &)>
(second call call).
-- Edit --
The OP precise that
at some point, I need to declare a type of the lambda parameter to unpack into, then pass that into the lambda...
The OP also precise that can use C++17 so... what about deduce T
, from F
, using std::function
deduction guides?
Given that your callback
callable receive only an argument, you could write (sorry: code not tested)
template <typename F>
void addSubscriberListener (const std::string & endpoint, F callback)
{
using T = std::remove_cv_t<
std::remove_reference_t<
typename decltype(std::function{callback})::argument_type;
// add to subscribers list
}
Unfortuntely std::function::argument_type
is available only if the std::function
receive only one argument and, furthermore, is deprecated in C++17 and removed from C++20.
So maybe you could write a custom type traits as follows
template <typename>
struct getTypeFirstArg;
template <typename R, typename T1, typename ... Ts>
struct getTypeFirstArg<std::function<R(T1, Ts...)>>
{ using type = T1; };
and extract T
with
using T = std::remove_cv_t<
std::remove_reference_t<
typename getTypeFirstArg<decltype(std::function{f})>::type>>;
The following is a full compiling example
#include <functional>
#include <type_traits>
template <typename>
struct getTypeFirstArg;
template <typename R, typename T1, typename ... Ts>
struct getTypeFirstArg<std::function<R(T1, Ts...)>>
{ using type = T1; };
template <typename F>
void foo (F f)
{
using T = std::remove_cv_t<
std::remove_reference_t<
typename getTypeFirstArg<decltype(std::function{f})>::type>>;
static_assert( std::is_same_v<T, int> );
}
int main ()
{
foo([&](int const &){});
}
See also the answer from Guillaume Racicot that should works also in C++11 and C++14.
Upvotes: 4
Reputation: 41820
You should skip the std::function
and use the lambda directly:
template <typename F>
void addSubscriberListener(std::string const& endpoint, F callback)
{
// add to subscribers list
}
This makes type deduction available for the lambda type
at some point, I need to declare a type of the lambda parameter to unpack into, then pass that into the lambda..
So if I understand you need to reflect on the parameter type of the lambda?
That can be done using a similar pattern to boost::callable:
template<typename>
struct extract_arg_types {};
template<typename R, typename C, typename Arg>
struct extract_arg_types<R(C::*)(Arg) const> {
using arg_type = Arg;
};
Then you can use that in your class:
template <typename F>
void addSubscriberListener(std::string const& endpoint, F callback)
{
using T = typename extract_arg_types<decltype(&T::operator())>::arg_type;
// add to subscribers list
}
Upvotes: 1