将模板函数转换为通用 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 是如何受到限制的......它是 -&gt; 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_asserts 中使用它:

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

传递给模板函数时,lambda自动衰减到函数指针

就内存使用而言,模板 + 仿函数/lambdas 不是最理想的吗?

将 lambda 作为模板参数传递给函数指针函数模板化

将 lambda 参数完美转发给成员函数,其中成员函数是非类型模板形参