代码之家  ›  专栏  ›  技术社区  ›  Enlico

当std::array是泛型lambda的输入时,为什么它不是常量表达式?

  •  0
  • Enlico  · 技术社区  · 3 年前

    (房地产 this other question of mine ; 如果你也看一下,我真的很感激。)

    If std::array<T,N>::size is constexpr 那么,为什么以下代码甚至不能编译呢?

    #include <array>
    #include <iostream>
    
    constexpr auto print_size = [](auto const& array){
        constexpr auto size = array.size();
        std::cout << size << '\n';
    };
    
    int main() {
        print_size(std::array<int,3>{{1,2,3}});
    }
    

    错误如下:

    $ g++ -std=c++17 deleteme.cpp && ./a.out 
    deleteme.cpp: In instantiation of ‘<lambda(const auto:1&)> [with auto:1 = std::array<int, 3>]’:
    deleteme.cpp:10:42:   required from here
    deleteme.cpp:5:20: error: ‘array’ is not a constant expression
        5 |     constexpr auto size = array.size();
          |                    ^~~~
    

    但我想知道为什么。

    在lambda调用站点,参数在编译时是已知的,应该用以下方式实例化lambda auto 等于 std::array<int,3> ,在哪里 3 是一个编译时值,因此应该输出 array.size() .

    我的推理出了什么问题?

    顺便说一句,如果我使用模板函数而不是通用lambda,情况也是如此。

    0 回复  |  直到 3 年前
        1
  •  3
  •   ecatmur    3 年前

    问题是 [expr.const]/5.12 :

    5-表达式E是一个核心常数表达式,除非根据抽象机的规则([intro.execution])对E的求值将计算以下值之一: [...]

    • (5.12)引用引用类型的变量或数据成员的id表达式,除非引用有前面的初始化,并且
      • (5.12.1)可用于常量表达式或
      • (5.12.2)其寿命始于E的评估;

    由于变量 array 是一个引用,不允许对其求值(在表达式内 array.size() ),尽管评估实际上什么也没做。

    经过 阵列 按价值( const 或无- const )使代码有效:

    constexpr auto print_size = [](auto const array){
        constexpr auto size = array.size(); // ok
        std::cout << size << '\n';
    };
    

    但是引用该参数并在下一行使用它是无效的:

    constexpr auto print_size = [](auto const arr){
        auto const& array = arr;
        constexpr auto size = array.size(); // error
        std::cout << size << '\n';
    };
    

    请注意,gcc 9错误地接受了此代码;只有在版本10之后,gcc才做到了这一点。

    gcc10在相关领域仍不合规;它接受呼叫a static constexpr 引用上的成员函数。 Using a constexpr static member of a reference as template argument 这是不正确的,clang正确地拒绝了它:

    struct S { static constexpr int g() { return 1; } };
    void f(auto const& s) {
        constexpr auto x = s.g(); // error
        constexpr auto y = decltype(s)::g(); // ok
    }
    int main() { f(S{}); }
    

    附录:根据论文,这可能会在未来发生变化 P2280R1 .

        2
  •  1
  •   Enlico    3 年前

    我在看 the 2014 Metaprogramming with Boost.Hana: Unifying Boost.Fusion and Boost.MPL presentation ,Louise Dionne谈到了这个话题,并解释了@super在评论中告诉我的内容,但我不理解。

    这是我对这个概念的重新表述:没有所谓的 constexpr 函数参数,因此每当lambda(实际上是它的底层) operator() )为给定类型实例化 array ,那 单一的 实例化 就是那个 这应该对双方都有用 常量表达式 和非- 常量表达式 这种类型的参数。

    正如Louis Dionne在相关演示中所说,

    []您无法生成 常量表达式 在函数内部,如果函数依赖于参数[],则函数的返回类型可能仅取决于其参数的类型,而不取决于它们的值[]

    这为解决这个问题提供了一种方法。使用 阵列 的类型,不使用 阵列 的值:

    constexpr auto print_size = [](auto const& array){
        using array_type = decltype(array);
        constexpr auto size = array_type{}.size();
        std::cout << size << '\n';
    };
    

    我认为这与@Jarod42在评论中的建议本质上没有什么不同:

    您可以使用 constexpr auto size = std::tuple_size<std::decay_t<decltype(array)>>::value

    此外,我玩了更多,因为最后一件事困扰着我: std::array 不是价值的一部分,但它是 类型 ,那我为什么不能打电话 size 成员功能 contexpr s原因是 std::array<T,N>::size() is sadly not static 如果是,可以按照下面的注释行( struct A 用于比较):

    #include <array>
    #include <iostream>
    #include <type_traits>
    
    template<std::size_t N>
    struct A {
        static constexpr std::size_t size() noexcept { return N; }
    };
    constexpr auto print_size = [](auto const& array){
        constexpr auto size = std::decay_t<decltype(array)>::size();
        std::cout << size << '\n';
    };
    
    int main() {
        //print_size(std::array<int,3>{{1,2,3}});
        print_size(A<3>{});
    }