为什么 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,同样如此。
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()
内),即使求值实际上没有做任何事情。
按值传递 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
成员函数。 这是不正确的,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>{});
}
(实际为
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,同样如此。
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()
内),即使求值实际上没有做任何事情。
按值传递 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
成员函数。
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>{});
}