Reputation: 264331
I am writing a library where the user provides a callback as a lambda. In the default scenario I want to just call the lambda and pass the back an object.
Now there are non trivial senarios where the user may want the context as well. So I want to be able to use the same callback mechanism and just allow the user to add a context as a parameter to their lambda and I will then pass both the object and the context.
I can't quite get SFINAE to work.
I have simplified the code to this:
#include <string>
#include <iostream>
class Context {};
template<typename F>
struct UseContext
{
// I want to set this value to 0 or 1 based on the parameters
// in F but can't quite get this to work.
enum {value = 0 };
};
template<typename F, typename T, bool useContext = UseContext<F>::value>
struct Caller;
template<typename F, typename T>
struct Caller<F, T, true>
{
void operator()(F& func, Context& context, T& object)
{
func(context, object);
}
};
template<typename F, typename T>
struct Caller<F, T, false>
{
void operator()(F& func, Context&, T& object)
{
func(object);
}
};
template<typename T, typename F>
void doWork(F&& func)
{
Context context;
T object;
/// STUFF
Caller<F,T> caller;
caller(func, context, object);
}
Usage:
int main()
{
// if UseContext::value == 0 then this compiles.
// This is the normal situation.
doWork<std::string>([](std::string const& x){ std::cout << x << "\n";});
// if UseContext::value == 1 then this compiles.
// This is if the user wants more context about the work.
// most of the time this extra parameter is not required.
// So I don't want to force the user to add it to the parameter
// list of the lambda.
doWork<std::string>([](Context&, std::string const& x){ std::cout << x << "\n";});
}
Or if there is a better way of doing this.
Upvotes: 2
Views: 478
Reputation: 137311
Expression SFINAE:
template<class F, class T>
auto call(F& func, Context& context, T& object) -> decltype(func(context, object), void())
{
func(context, object);
}
template<class F, class T>
auto call(F& func, Context&, T& object) -> decltype(func(object), void())
{
func(object);
}
Then just call(func, context, object)
. This is ambiguous if both forms are valid. If you want to disambiguate, just add a dummy parameter and do the usual int
/long
trick.
Upvotes: 5
Reputation: 26476
My solution is to use std::is_constructible
plus std::enable_if
:
template<typename F,typename T>
typename std::enable_if<std::is_constructible<std::function<void(T const&)>,F>::value>:type doWork(F func)
{
//...
}
template<typename F,typename T>
typename std::enable_if<std::is_constructible<std::function<void(Context&,T const&)>,F>::value>:type doWork(F func)
{
//...
}
explenation - each std::function
can be built from the equivilant lambda. here we are testing using std::enable_if
if you can build a std::function<void(T)>
or a std::function<void(Context,T)>
and re-wire the correct function in compile time.
Upvotes: 1