将模板函数转换为通用lambda

时间:2017-07-19 10:06:18

标签: c++ lambda c++14 enable-if

我希望将模板化函数传递给它们,就像它们是通用lambda一样,但是这不起作用。

#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,但事实并非如此。

  • 是否有建议将模板函数隐式转换为通用约束lambda(案例2/4)?如果不是,是否存在阻止这样做的基本语言原因?
  • 截至目前,我必须使用案例2,这非常麻烦。
    • 案例4:不符合c ++ 14标准,即使在c++20中应该是标准的,但仍然不完美(因为你创建一个从根本上不添加任何信息的lambda,所以很冗长)。
    • 案例3:不受约束,但我依赖(此处未显示)替换失败以调用&#34; print&#34;与非&#34;矢量&#34;参数(P0428R1提到了这个问题)。所以我想辅助问题是&#34;我可以用一些enable_if技巧来约束一个普通的lambda吗?&#34;

在C ++ 14/17/20中,是否有非常简洁的方式来实现从案例1到案例2的转换?我甚至对宏观黑客持开放态度。

2 个答案:

答案 0 :(得分:9)

  

在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?

是的,请查看P0119N3617。不确定他们的状态。

答案 1 :(得分:3)

  

我可以使用一些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{});
}

如果您将注释切换到最后一行,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上运行的示例代码。