Преобразование функции шаблона в общую лямбду

Я хотел бы передать шаблонные функции, как если бы они были общими лямбдами, однако это не работает.

#include <iostream>
#include <vector>
#include <tuple>
#include <string>
#include <utility> 


// for_each with std::tuple
// (from https://stackoverflow.com/a/6894436/1583122)
template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
for_each(std::tuple<Tp...> &, FuncT)
{}

template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
for_each(std::tuple<Tp...>& t, FuncT f) {
    f(std::get<I>(t));
    for_each<I + 1, FuncT, Tp...>(t, f);
}

// my code
template<class T> auto
print(const std::vector<T>& v) -> void {
    for (const auto& e : v) {
        std::cout << e << "\t";
    }
}

struct print_wrapper {
    template<class T>
    auto operator()(const std::vector<T>& v) {
        print(v);
    }
};

auto print_gen_lambda = [](const auto& v){ print(v); };

auto print_gen_lambda_2 = []<class T>(const std::vector<T>& v){ print(v); }; // proposal P0428R1, gcc extension in c++14/c++17

int main() {
     std::tuple<std::vector<int>,std::vector<double>,std::vector<std::string>> t = { {42,43},{3.14,2.7},{"Hello","World"}};
    for_each(t, print); // case 1: error: template argument deduction/substitution failed: couldn't deduce template parameter 'FuncT'
    for_each(t, print_wrapper()); // case 2: ok
    for_each(t, print_gen_lambda); // case 3: ok
    for_each(t, print_gen_lambda_2); // case 4: ok
}

Обратите внимание, что случаи 2 и 4 строго эквивалентны. Случай 3 является более общим, но неограниченным (это проблема для меня). Я думаю, что случай 1 должен трактоваться языком аналогично случаям 2 и 4, однако это не так.

  • Есть ли предложение неявно преобразовать шаблонную функцию в общую ограниченную лямбду (случай 2/4)? Если нет, то есть ли фундаментальная языковая причина, препятствующая этому?
  • As of now, I have to use case 2, which is quite cumbersome.
    • case 4: not c++14-compliant, even if should be standard in c++20, and still not perfect (verbose since you create a lambda that fundamentally does not add any information).
    • случай 3: не ограничен, но я полагаюсь (здесь не показан) на ошибку подстановки для вызовов «print» с не «векторными» аргументами (P0428R1 упоминает эту проблему). Итак, я предполагаю, что второстепенный вопрос: «Могу ли я ограничить общую лямбду некоторыми трюками enable_if?»

Есть ли в С ++ 14/17/20 очень краткий способ преобразования из случая 1 в случай 2? Я открыт даже для макросов.


person Bérenger    schedule 19.07.2017    source источник
comment
Для всех, кто задается вопросом, проблема заключается в том, что print фактически не называет функцию.   -  person StoryTeller - Unslander Monica    schedule 19.07.2017
comment
да. Хитрость в том, что функция-шаблон не является типом, тогда как нешаблонные классы с функциями-членами шаблона являются просто обычными типами.   -  person Bérenger    schedule 19.07.2017


Ответы (2)


Есть ли в С ++ 14/17/20 очень краткий способ преобразования из случая 1 в случай 2? Я открыт даже для макросов.

да.

// C++ requires you to type out the same function body three times to obtain
// SFINAE-friendliness and noexcept-correctness. That's unacceptable.
#define RETURNS(...) noexcept(noexcept(__VA_ARGS__)) \
     -> decltype(__VA_ARGS__){ return __VA_ARGS__; }

// The name of overload sets can be legally used as part of a function call -
// we can use a macro to create a lambda for us that "lifts" the overload set
// into a function object.
#define LIFT(f) [](auto&&... xs) RETURNS(f(::std::forward<decltype(xs)>(xs)...))

Затем вы можете сказать:

for_each(t, LIFT(print)); 

Есть ли предложение неявно преобразовать функцию шаблона в общую ограниченную лямбду?

Да, посмотрите P0119 или N3617. Не уверен в их статусе.

person Vittorio Romeo    schedule 19.07.2017
comment
Спасибо, это именно то, что я искал. Я не уверен, что понимаю, как ограничивается лямбда ... Это -> decltype(__VA_ARGS__)? - person Bérenger; 19.07.2017
comment
@ Bérenger: да, decltype, содержащий тело, ограничивает лямбду, так как это удобно для SFINAE. - person Vittorio Romeo; 19.07.2017

Могу ли я ограничить общую лямбду некоторыми уловками enable_if?

Если все, что вы хотите, - это ограничить типы параметров вашего универсального лямбда-выражения, вы можете сделать это с помощью пары объявлений функций (определение не требуется) и static_assert (так что вы получите изящное сообщение об ошибке во время компиляции в в случае отказа). Никаких макросов (они такие C-ish).

Это следует минимальному рабочему примеру:

#include<vector>
#include<type_traits>
#include<utility>
#include<list>

template<template<typename...> class C, typename... A>
constexpr std::true_type spec(int, C<A...>);

template<template<typename...> class C, template<typename...> class T, typename... A>
constexpr std::false_type spec(char, T<A...>);

int main() {
    auto fn = [](auto&& v) {
        static_assert(decltype(spec<std::vector>(0, std::declval<std::decay_t<decltype(v)>>()))::value, "!");
        // ...
    };

    fn(std::vector<int>{});
    // fn(std::list<int>{});
    //fn(int{});
}

Если вы переключите комментарии на последние строки, static_assert выдаст ошибку, и компиляция завершится неудачно, как и ожидалось.
Посмотрите, как это работает на wandbox.


Примечание.

Конечно, вы можете уменьшить шаблон здесь:

static_assert(decltype(spec<std::vector>(0, std::declval<std::decay_t<decltype(v)>>()))::value, "!");

Добавьте шаблон переменной, подобный следующему:

template<template<typename...> class C, typename T>
constexpr bool match = decltype(spec<C>(0, std::declval<std::decay_t<T>>()))::value;

Затем используйте его в своих static_asserts:

static_assert(match<std::vector, decltype(v)>, "!");

Довольно ясно, не правда ли?


Примечание.

В C ++ 17 вы сможете еще больше сократить код, необходимый для этого, определив лямбда как:

auto fn = [](auto&& v) {
    if constexpr(match<std::vector, decltype(v)>) {
        print(v);
    }
};

См. Пример кода, запущенного на wandbox.

person skypjack    schedule 19.07.2017
comment
Да, это вызовет ошибку, но его нельзя использовать с SFINAE, см. P0428R1 (open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0428r1.pdf) - person Bérenger; 19.07.2017
comment
Что ж, match верно, если v - вектор. Вы можете определить одну функцию for_each и использовать эту информацию непосредственно в лямбда-выражении (в C ++ 17 это можно сделать с помощью if constexpr). static_assert является частью примера кода, вы можете его удалить. :-) - person skypjack; 19.07.2017
comment
@ Bérenger Вот как ваш пример будет выглядеть в C ++ 17. - person skypjack; 19.07.2017