【问题标题】:Is there a way to pass a `constexpr` value into lambda so that it remains `constexpr` inside that lambda?有没有办法将“constexpr”值传递给 lambda,使其在 lambda 中保持“constexpr”?
【发布时间】:2018-11-28 15:22:37
【问题描述】:

这就是我想做的;发布整个代码,因为它不会太长,也可以演示我要解决的具体任务。基本上,我需要一种按索引从参数包中迭代值的方法(索引部分很重要,即使在此示例中不需要它)。

#include <iostream>
#include <tuple>
#include <type_traits>

template <int First, int Last, typename Functor>
constexpr void static_for(Functor&& f)
{
    if constexpr (First < Last)
    {
        f(std::integral_constant<int, First>{});
        static_for<First + 1, Last, Functor>(std::forward<Functor>(f));
    }
}

template <size_t index, typename... Args>
auto value_by_index(Args&&... args) noexcept {
    return std::get<index>(std::forward_as_tuple(std::forward<Args>(args)...));
}

template <typename... ValueTypes>
void traverse(ValueTypes... values)
{
    static_for<0, sizeof...(ValueTypes)>([&](int i) {
        auto v = value_by_index<static_cast<size_t>(i), ValueTypes...>(values...);
        std::cout << v << std::endl;
    });
}

int main()
{
    traverse(0.0f, 1, 3.33, "str");

    return 0;
}

编译错误,当然是:

<source>:24:71: error: 'i' is not a constant expression

如果 lambda 可以有显式模板参数,i 就是这样一个参数,编译器在编译时就知道它是显而易见的。但这不是 lambda 的工作方式。

如果您想将其视为 XY 问题,我想我不需要在我的 static_for 中专门调用 lambda,但我确实需要调用一些可以访问参数包的代码traverse 的索引,如果 traverse 是成员函数,我需要访问它的 this

在线试用:https://godbolt.org/z/eW4rnm

【问题讨论】:

    标签: c++ templates c++17 template-meta-programming constexpr


    【解决方案1】:

    使用通用 lambda 和 constexpr 转换运算符

    template <typename... ValueTypes>
    void traverse(ValueTypes... values)
    {
        static_for<0, sizeof...(ValueTypes)>([&](auto I)
        //                                       ~~~^
        {
            auto v = value_by_index<I>(values...);
            //                     ~^~
            std::cout << v << std::endl;
        });
    }
    

    DEMO

    为 lambda 表达式 使用模板参数列表:

    template <typename... ValueTypes>
    void traverse(ValueTypes... values)
    {
        static_for<0, sizeof...(ValueTypes)>([&]<int I>(std::integral_constant<int, I>)
        //                                       ~~~~^                             ~^~
        {
            auto v = value_by_index<I>(values...);
            //                     ~^~
            std::cout << v << std::endl;
        });
    }
    

    DEMO 2

    【讨论】:

    • 哇,我犯了这么愚蠢的错误!好像i的类型是integral_constant,而不是int
    • @VioletGiraffe 是的,并且类型本身在编译时是已知的。看到我没有使用i的值
    • 由于std::integral_constant&lt;int, I&gt;可以转换为int,转换函数为constexpr,应该可以用i代替decltype(i)::value
    • 好吧compilers accept value_by_index&lt;i&gt;(values...),这是有道理的,因为转换函数不需要读取i的值。
    • 起初我不明白,但现在我明白了你在那里做了什么。您已将值 (int) 转换为模板类型,将该值作为非类型模板参数保存。 std::integral_constant 方便地为整数类型提供了这样的包装器;可以为其他类型的值编写自定义持有人模板。 lambda 不会将其值参数视为constexpr,但它确实会看到调用它的特定类型,并且有constexpr 从类型到它所持有的值的转换。聪明。
    【解决方案2】:

    来不及玩了?

    基本上,我需要一种按索引从参数包中迭代值的方法(索引部分很重要,即使在此示例中不需要它)。

    对不起,但是... std::make_index_sequencestd::index_sequence 的旧用法怎么样?

    维护您的value_by_index(),我提出以下基于traverse()traverse_helper() 的C++14 解决方案

    template <typename F, std::size_t ... Is, typename ... VTs>
    void traverse_helper (F f, std::index_sequence<Is...>, VTs ... vs)
     { 
       using unused = int[];
    
       (void)unused { 0, (f(value_by_index<Is>(vs...)), 0)... };
     }
    
    template <typename F, typename ... VTs>
    void traverse (F f, VTs ... vs)
     { traverse_helper(f, std::make_index_sequence<sizeof...(VTs)>{}, vs...); }
    

    请注意,我也将可调用对象作为参数传递。

    如果您可以使用 C++17(如您标记的那样),traverse_helper() 就变成了

    template <typename F, std::size_t ... Is, typename ... VTs>
    void traverse_helper (F f, std::index_sequence<Is...>, VTs ... vs)
     { (f(value_by_index<Is>(vs...)), ...); }
    

    您可以拨打traverse()如下

    traverse([](auto x){ std::cout << x << std::endl; },
             0.0f, 1, 3.33, "str");
    

    以下是完整的 C++14 编译示例

    #include <iostream>
    #include <tuple>
    #include <type_traits>
    
    template <std::size_t I, typename ... As>
    auto value_by_index (As && ... as) noexcept
     { return std::get<I>(std::forward_as_tuple(std::forward<As>(as)...)); }
    
    template <typename F, std::size_t ... Is, typename ... VTs>
    void traverse_helper (F f, std::index_sequence<Is...>, VTs ... vs)
     { 
       using unused = int[];
    
       (void)unused { 0, (f(value_by_index<Is>(vs...)), 0)... };
     }
    
    template <typename F, typename ... VTs>
    void traverse (F f, VTs ... vs)
     { traverse_helper(f, std::make_index_sequence<sizeof...(VTs)>{}, vs...); }
    
    int main ()
     {
        traverse([](auto x){ std::cout << x << std::endl; },
                 0.0f, 1, 3.33, "str");
     }
    

    【讨论】:

    • 有趣!我不得不承认,我不明白 index_sequence 的作用和用途。要去读它。
    • @VioletGiraffe - std::index_sequencestd::make_index_sequence 允许生成序列,是使用可变参数模板的非常有用的工具。
    猜你喜欢
    • 2016-06-10
    • 2015-10-22
    • 2020-12-24
    • 1970-01-01
    • 2017-01-07
    • 1970-01-01
    • 2023-01-01
    • 1970-01-01
    • 2015-11-21
    相关资源
    最近更新 更多