为什么 std::array 作为模板函数/泛型 lambda 的输入时不是常量表达式?

问题描述 投票:0回答:2

(涉及到我的另一个问题;如果你也看看这个问题,我将非常感激。)

如果

std::array<T,N>::size
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()
的输出.

我的推理有什么问题吗?

c++ arrays templates lambda constexpr
2个回答
3
投票

问题是[expr.const]/5.12:

5 - 表达式 E 是核心常量表达式,除非对 E 的求值遵循抽象机 ([intro.execution]) 的规则,将求值以下其中一项: [...]

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

由于变量

array
是一个引用,因此不允许对其求值(在表达式
array.size()
内),即使求值实际上没有执行任何操作。

按值传递

array
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 才得到了正确的结果。

gcc 10 在相关领域仍然不合规;它接受在引用上调用

static constexpr
成员函数。 使用引用的 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,这将来可能会改变。


1
投票

我正在观看 2014 使用 Boost.Hana 进行元编程:统一 Boost.Fusion 和 Boost.MPL 演示文稿,其中 Louise Dionne 谈到了这个主题并解释了 @super 在评论中告诉我的内容,但我不明白。 .

这是我对这个概念的重新表述:不存在

constexpr
函数参数这样的东西,因此每当 lambda(实际上是其底层
operator()
)为给定类型的
array
实例化时,那个 single 实例化 应该适用于该类型的 constexpr
 和非 
constexpr
 参数。

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

[...] 如果函数依赖于参数,则无法在函数内生成

constexpr

 [...] 函数的返回类型可能仅取决于其参数的类型,而不取决于它们的值 [...]

这提供了解决该问题的方法。使用

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 在评论中建议的没有什么不同:

您可能会使用

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


作为补充,我又玩了一些,因为最后一件事困扰着我:

std::array

的大小不是值的一部分,而是
type的一部分,所以为什么我不能调用
size
中的contexpr
成员函数?原因是 
std::array<T,N>::size()
 遗憾的是不是 
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>{}); }
    
© www.soinside.com 2019 - 2024. All rights reserved.