2016-11-10 2 views
3

У меня есть класс Foo, который принимает различные варианты предикатов через свой конструктор.Передача различных шаблонов функции lambdas в C++

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); })) 
    { 
    } 
}; 

Это позволяет мне строить класс с явным function объекта:

Value<int> i; 
Foo foo0(i, function<bool()>([]() { return true; })); 
Foo foo1(i, function<bool(const Value<int> &)>([](const auto &) { return true; })); 

однако он терпит неудачу, когда пытаются использовать лямбда непосредственно:

Foo fooL1(i, [](const Value<int> &) { return true; }); 

По причине я не» t понять, однако компилятор не считает наличие неявного преобразования из лямбда в function в шаблон конструктора. Сообщение об ошибке (Visual C++ 2015, Update 3):

ошибка C2664: 'Foo :: Foo (Foo & &)': не удается преобразовать аргумент 2 из 'главного :: < lambda_f1d2143f356d549800fb3412d8bc61a2>' в 'станд :: функция < BOOL (аннулируются)>'

Теперь я могу добавить еще один шаблон конструктора для лямбды

template<typename T, typename UnaryPredicate> 
Foo(Value<T> &value, UnaryPredicate predicate) : 
    Foo(value, function<bool(const Value<T> &)>(predicate)) 
{ 
} 

, который будет работать хорошо до тех пор, пока лямбда передается этот конструктор имеет один параметр Value<T>, однако, естественно, не выполняется для лямбды без параметра:

Foo fooL0(i, []() { return true; }); 

Так что я, вероятно, потребуется некоторое SFINAE магию, чтобы включить соответствующий шаблон конструктора для разные лямбды, что-то вроде:

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)) 
{ 
} 

Или, может быть, только один шаблон конструктор может сделать трюк, что-то вроде:

template<typename T, typename UnaryPredicate> 
Foo(Value<T> &value, UnaryPredicate predicate) : 
    Foo(value, function<???decltype(UnaryPredicate)???>(predicate)) 
{ 
} 

Или, может быть, совсем другое решение? Вопрос заключается в том, как включить перегрузки конструктора для работы с соответствующими lambdas.

ответ

4

Ваша проблема в том, что C++ рассматривает все аргументы одинаково и пытается вывести ваши шаблонные аргументы из всех них.

Неспособность использовать аргументы шаблона - это ошибка, а не просто несогласованный вывод. Он просто не принимает те, которые соответствуют и «идут с ним».

Мы можем отметить аргумент шаблона, как не-выведено:

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>>; 

затем:

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); } 
) 
{} 

Теперь T выводится только из первого аргумента. Нормальное преобразование происходит для 2-го.

(Незначительные изменения форматирования/оптимизация/сокращение кода относится к вашему Foo за пределами block_deduction.)

+0

Отлично, хотя я не вижу позади волшебства, он работает как шарм. Можете ли вы в конце концов указать мне какую-либо информацию, описывающую эту технику в читаемой человеком форме? Благодарю. – manison

+1

@manisin block deducton принимает тип и возвращает тот же тип. Но возвращаемый тип больше не выводим. Выделение типа шаблона - это соответствие шаблонов, а 'tag_t :: type' не может быть сопоставлен с шаблоном. Короче, потому что стандарт говорит так. Почему это так? Теоретически ':: type' может быть не связан с' T', на самом деле это 'T'. C++ не требует от системы вывода типов инвертировать произвольные Turing-процессы, а 'tag_t :: type' может иметь произвольное отображение из' T' в ':: type'. – Yakk

Смежные вопросы