问题
The size()
member functions of std::initializer_list
and std::array
have identical signatures:
constexpr size_type size() const noexcept;
Both are constexpr
. However, std::array::size()
can be used in constexpr
context, but std::initializer_list::size()
can't:
std::initializer_list<int> il{1, 2, 3, 4};
constexpr std::size_t il_size = il.size(); // (1) - fails with GCC and Clang (*)
std::array<int, 4> arr{1, 2, 3, 4};
constexpr std::size_t arr_size = arr.size(); // (2) - OK
(*) The error is:
in 'constexpr' expansion of 'il.std::initializer_list<int>::size()' error: the value of 'il' is not usable in a constant expression
As far as I understand, the fact that (1) fails and (2) succeeds is perfectly reasonable because constexpr
member function of a class template may fail to satisfy constexpr
requirements.
I have two related questions:
- Why isn't
std::initializer_list
implemented in a way such that(1)
compiles? Is there something in the standard that prevents such an implementation? Given that
(1)
fails, what is the purpose of markingstd::initializer_list::size()
asconstexpr
? The only use case seems to be this one:constexpr std::initializer_list<int> il{1, 2, 3, 4}; // note constexpr constexpr std::size_t il_size = il.size();
回答1:
Why isn't
std::initializer_list
implemented in a way such that (1) compiles? Is there something in the standard that prevents such an implementation?
Yes, it's impossible. An initializer_list
can have any size, you cannot during constant evaluation time get the size of an arbitrary runtime initializer_list
. This is quite different from std::array
, where a given std::array<T, N>
has size N
. One's size is variable, the other's is fixed.
This isn't really different from any other variable:
struct X { int i; };
X x{42};
constexpr X cx{17};
constexpr int i = x.i; // error
constexpr int ci = cx.i; // ok
Given that (1) fails, what is the purpose of marking
std::initializer_list::size()
asconstexpr
? The only use case seems to be this one
This is not the only use case, far from. constexpr
member functions do not just permit you to invoke them on constexpr
objects. They more generally permit you to invoke them anywhere during constant evaluation time.
That is, during any kind of constant evaluation, if you create an initializer_list
, you can then use its size. A silly minimal example might be:
constexpr size_t four() {
std::initializer_list<int> lst = {1, 2, 3, 4};
return lst.size();
}
static_assert(four() == 4);
Note that lst
itself is not a constexpr
object, it's just some ephemeral thing that got created during the course of evaluating the call to four()
during constant evaluation. But we still need that size()
to be constexpr
- invoking any non-constexpr
function is a no-no.
From here you can extend it outwards to any arbitrary code you might want to run that at some point, during constant evaluation, wants to determine the size of an std::initializer_list
.
回答2:
It is not that std::initializer_list::size
cannot be used in a constant expression. For example, the following probably (see footnote) should compile:
#include <initializer_list>
int main() {
constexpr int x = (std::initializer_list<int>{1, 2, 3, 4}).size();
static_assert(x == 4);
}
However, the way you have called it, what presumably happens is that the size
method has to perform an lvalue-to-rvalue conversion on a member of the std::initializer_list
object and that violates the constraints on constant expressions (C++17 [expr.const]/(2.7)).
In my example, the corresponding lvalue-to-rvalue conversion occurs on "a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within
the evaluation of e
;", which makes it allowed (C++17 [expr.const]/(2.7.4)).
In the std::array
example, there is probably no lvalue-to-rvalue conversion, since the template parameter is returned.
Footnote: See LWG 2833.
来源:https://stackoverflow.com/questions/59058271/constexpr-ness-of-stdinitializer-listsize-vs-stdarraysize