问题
I have a class Foo
that accepts different predicate variants through its constructor.
template<typename T>
struct Value
{
T value;
};
class Foo
{
public:
template<typename T>
Foo(Value<T> &value, function<bool()> predicate)
{
}
template<typename T>
Foo(Value<T> &value, function<bool(const Value<T> &)> predicate) :
Foo(value, function<bool()>([&value, predicate](){ return predicate(value); }))
{
}
};
This allows me to construct the class with explicit function
object:
Value<int> i;
Foo foo0(i, function<bool()>([]() { return true; }));
Foo foo1(i, function<bool(const Value<int> &)>([](const auto &) { return true; }));
however it fails when trying to use a lambda directly:
Foo fooL1(i, [](const Value<int> &) { return true; });
For a reason I don't understand yet the compiler does not consider availability of implicit conversion from lambda to function
in constructor template. The error message is (Visual C++ 2015, Update 3):
error C2664: 'Foo::Foo(Foo &&)': cannot convert argument 2 from 'main::< lambda_f1d2143f356d549800fb3412d8bc61a2>' to 'std::function< bool (void)>'
Now I can add another constructor template for lambdas
template<typename T, typename UnaryPredicate>
Foo(Value<T> &value, UnaryPredicate predicate) :
Foo(value, function<bool(const Value<T> &)>(predicate))
{
}
which will work fine as long as the lambda passed to that constructor has one parameter of Value<T>
, however it naturally fails for lambdas without parameter:
Foo fooL0(i, []() { return true; });
So I would probably need some SFINAE magic to enable appropriate constructor template for different lambdas, something like:
template<typename T, typename UnaryPredicate,
typename = enable_if_t<is_callable_without_args> >
Foo(Value<T> &value, UnaryPredicate predicate) :
Foo(value, function<bool()>(predicate))
{
}
template<typename T, typename UnaryPredicate,
typename = enable_if_t<is_callable_with_one_arg> >
Foo(Value<T> &value, UnaryPredicate predicate) :
Foo(value, function<bool(const Value<T> &)>(predicate))
{
}
Or maybe only one constructor template could do the trick, something like:
template<typename T, typename UnaryPredicate>
Foo(Value<T> &value, UnaryPredicate predicate) :
Foo(value, function<???decltype(UnaryPredicate)???>(predicate))
{
}
Or maybe a completely different solution? The question is how to enable constructor overloads to work with appropriate lambdas.
回答1:
Your problem is that C++ treats all arguments equally, and attempts to deduce your template arguements from all of them.
Failure to deduce the template arguments used is an error, not just inconsistent deduction. It just doesn't take the ones that match and "go with it".
We can mark a template argument as non-deduced:
template<class T> struct tag_t {using type=T;};
template<class Tag> using type=typename Tag::type;
template<class T>
using block_deduction = type<tag_t<T>>;
then:
template<class T>
Foo(
Value<T> &value,
block_deduction<function<bool(const Value<T> &)>> predicate
) :
Foo(
value,
[&value, predicate=std::move(predicate)]{ return predicate(value); }
)
{}
now T
is deduced only from the first argument. Normal conversion occurs for 2nd.
(Minor formatting changes/optimizations/code shortening applies to your Foo
beyond block_deduction
also included.)
来源:https://stackoverflow.com/questions/40523248/passing-different-lambdas-to-function-template-in-c