为什么 std::array 作为模板化 function/generic lambda 的输入时不是常量表达式?

Why a std::array is not constant expression when it is the input of a templated function/generic lambda?

(实际为 ;如果您也看一下,我将不胜感激。)

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,同样如此。

问题是[expr.const]/5.12:

5 - An expression E is a core constant expression unless the evaluation of E, following the rules of the abstract machine ([intro.execution]), would evaluate one of the following: [...]

  • (5.12) an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either
    • (5.12.1) it is usable in constant expressions or
    • (5.12.2) its lifetime began within the evaluation of E;

由于变量 array 是一个引用,因此不允许对其求值(在表达式 array.size() 内),即使求值实际上没有做任何事情。

按值传递 arrayconst 或非 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 才正确。

gcc 10 在相关领域仍然不兼容;它接受在引用上调用 static constexpr 成员函数。 这是不正确的,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

这在未来可能会发生变化

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

这是我对这个概念的改写:没有 constexpr 函数参数这样的东西,因此每当为给定类型的 array,即 单个 实例化 是一个 应该同时适用于 constexpr 和非 constexpr 参数那种类型的。

正如 Louis Dionne 在链接的演示文稿中所说,

[…] you can't generate a constexpr inside a function if it depends on a parameter […] the return type of a function may only depend on the types of its arguments, not on their values […]

这提供了解决该问题的方法。使用 array 的类型而不使用 array 的值:

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

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

You might use constexpr auto size = std::tuple_size<std::decay_t<decltype(array)>>::value

作为补充,我尝试了更多,因为最后一件事困扰着我:std::array 的大小不是值的一部分,但它是 类型的一部分,那么为什么我不能在contexpr中调用size成员函数呢?原因是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>{});
}