Passing different lambdas to function template in c++

泪湿孤枕 提交于 2020-03-01 14:49:47

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!