Lambda 回调取决于其参数
Lambda callback depends on its parameters
我正在编写一个库,用户可以在其中以 lambda 形式提供回调。在默认情况下,我只想调用 lambda 并传回一个对象。
现在有一些重要的场景,用户可能也需要上下文。所以我希望能够使用相同的回调机制,只允许用户将上下文作为参数添加到他们的 lambda 中,然后我将传递对象和上下文。
我不能完全让 SFINAE 工作。
我已将代码简化为:
#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);
}
用法:
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";});
}
或者如果有更好的方法。
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);
}
然后call(func, context, object)
。如果两种形式都有效,这是不明确的。如果您想消除歧义,只需添加一个虚拟参数并执行通常的 int
/long
技巧。
我的解决办法是用std::is_constructible
加上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 - 每个 std::function
都可以从等价的 lambda 构建。我们在这里使用 std::enable_if
测试您是否可以构建 std::function<void(T)>
或 std::function<void(Context,T)>
并在编译时重新连接正确的函数。
我正在编写一个库,用户可以在其中以 lambda 形式提供回调。在默认情况下,我只想调用 lambda 并传回一个对象。
现在有一些重要的场景,用户可能也需要上下文。所以我希望能够使用相同的回调机制,只允许用户将上下文作为参数添加到他们的 lambda 中,然后我将传递对象和上下文。
我不能完全让 SFINAE 工作。
我已将代码简化为:
#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);
}
用法:
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";});
}
或者如果有更好的方法。
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);
}
然后call(func, context, object)
。如果两种形式都有效,这是不明确的。如果您想消除歧义,只需添加一个虚拟参数并执行通常的 int
/long
技巧。
我的解决办法是用std::is_constructible
加上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 - 每个 std::function
都可以从等价的 lambda 构建。我们在这里使用 std::enable_if
测试您是否可以构建 std::function<void(T)>
或 std::function<void(Context,T)>
并在编译时重新连接正确的函数。