将模板函数转换为通用 lambda
Posted
技术标签:
【中文标题】将模板函数转换为通用 lambda【英文标题】:Convert template function to generic lambda 【发布时间】:2017-12-24 12:50:16 【问题描述】:我想传递模板函数,就好像它们是通用 lambda 一样,但这不起作用。
#include <iostream>
#include <vector>
#include <tuple>
#include <string>
#include <utility>
// for_each with std::tuple
// (from https://***.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,但事实并非如此。
是否有建议将模板函数隐式转换为通用约束 lambda(案例 2/4)?如果不是,是否存在阻止这样做的基本语言原因? 到目前为止,我必须使用案例2,这很麻烦。 案例 4:不符合 c++14,即使在 c++20 中应该是标准的,但仍然不完美(冗长,因为您创建了一个基本上不添加任何信息的 lambda)。 案例 3:不受约束,但我依赖(此处未显示)使用非“向量”参数调用“打印”的替换失败(P0428R1 提到了这个问题)。所以我想次要问题是“我可以用一些 enable_if 技巧来约束一个通用的 lambda 吗?”在 C++14/17/20 中,是否有一种非常简洁的方式来实现从案例 1 到案例 2 的转换?我什至对宏黑客持开放态度。
【问题讨论】:
对于任何想知道的人来说,问题源于print
实际上没有命名函数。
是的。诀窍是模板函数不是类型,而具有模板成员函数的非模板类只是普通类型。
【参考方案1】:
在 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));
是否有将模板函数隐式转换为通用约束 lambda 的建议?
是的,看看P0119 或N3617。不确定他们的状态。
【讨论】:
谢谢,这正是我想要的。我不确定我是否理解 lambda 是如何受到限制的......它是-> decltype(__VA_ARGS__)
吗?
@Bérenger:是的,包含主体的decltype
限制了 lambda,因为它对 SFINAE 友好。【参考方案2】:
我可以使用一些 enable_if 技巧来约束通用 lambda 吗?
如果您想要的只是限制泛型 lambda 的参数类型,您可以通过几个函数声明(无需定义)和 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);
如果将 cmets 切换到最后一行,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 中,您可以通过将 lambda 定义为:
auto fn = [](auto&& v)
if constexpr(match<std::vector, decltype(v)>)
print(v);
;
查看您在 wandbox 上运行的示例代码。
【讨论】:
是的,它会抛出一个错误,但它不能与 SFINAE 一起使用,请参阅 P0428R1 (open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0428r1.pdf) 好吧,如果v
是向量,则match
为真。您可以定义单个 for_each
函数并直接在 lambda 中使用该信息(在 C++17 中,您可以通过 if constexpr
执行此操作)。 static_assert
是示例代码的一部分,您可以将其删除。 :-)
@Bérenger This 是您的示例在 C++17 中的样子。以上是关于将模板函数转换为通用 lambda的主要内容,如果未能解决你的问题,请参考以下文章
我可以将 C++17 无捕获 lambda constexpr 转换运算符的结果用作函数指针模板非类型参数吗?
通过 Cloudformation 模板将 Lambda 函数添加到 Kinesis Firehose