Преобразовать функцию шаблона в общую лямбду
Я хотел бы передать шаблонные функции, как если бы они были общими лямбдами, однако это не работает.
#include <iostream>
#include <vector>
#include <tuple>
#include <string>
#include <utility>
// for_each with std::tuple
// (from https://stackru.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)? Если нет, есть ли фундаментальная языковая причина, которая мешает это сделать?
- На данный момент я должен использовать случай 2, который является довольно громоздким.
- случай 4: не соответствует C++14, даже если он должен быть стандартным в C++ 20, и все же не идеален (многословно, поскольку вы создаете лямбду, которая принципиально не добавляет никакой информации).
- случай 3: не ограничен, но я полагаюсь (не показан здесь) на неудачу замещения при вызовах "print" с не "векторными" аргументами (P0428R1 упоминает эту проблему). Поэтому я думаю, что вспомогательный вопрос "Можно ли ограничить общую лямбду некоторыми трюками enable_if?"
Есть ли в C++14/17/20 очень краткий способ включить преобразование из случая 1 в случай 2? Я даже открыт для макросов.
2 ответа
Есть ли в C++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. Не уверен насчет их статуса.
Могу ли я ограничить общую лямбду некоторыми трюками enable_if?
Если все, что вам нужно, это ограничить типы параметров вашей общей лямбды, вы можете сделать это с помощью пары объявлений функций (определение не требуется) и static_assert
(так что вы получите изящную ошибку сообщения во время компиляции в случае сбоя). Вокруг вообще нет макросов (они такие классные).
Следует минимальный рабочий пример:
#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_assert
s:
static_assert(match<std::vector, decltype(v)>, "!");
Довольно ясно, не правда ли?
Заметка.
В C++17 вы сможете еще больше сократить код, необходимый для этого, определив свою лямбду как:
auto fn = [](auto&& v) {
if constexpr(match<std::vector, decltype(v)>) {
print(v);
}
};
Смотрите ваш пример кода, работающего на wandbox.