Is the following code legitimate?
template <int N>
class foo {
public:
constexpr foo()
{
for (int i = 0; i < N; ++i) {
v_[i] = i;
}
}
private:
int v_[N];
};
constexpr foo<5> bar;
Clang accepts it, but GCC and MSVC reject it.
GCC's error is:
main.cpp:15:18: error: 'constexpr foo<N>::foo() [with int N = 5]' called in a constant expression
15 | constexpr foo<5> bar;
| ^~~
main.cpp:4:15: note: 'constexpr foo<N>::foo() [with int N = 5]' is not usable as a 'constexpr' function because:
4 | constexpr foo()
| ^~~
main.cpp:4:15: error: member 'foo<5>::v_' must be initialized by mem-initializer in 'constexpr' constructor
main.cpp:12:9: note: declared here
12 | int v_[N];
| ^~
If this kind of code were OK, I could cut quite a few uses of index_sequence
s.
Manual initialization A constexpr array can be declared and initialized manually through: constexpr int arr[3] = {1,2,3};
A constructor that is declared with a constexpr specifier is a constexpr constructor. Previously, only expressions of built-in types could be valid constant expressions. With constexpr constructors, objects of user-defined types can be included in valid constant expressions.
A constexpr function is a function that can be invoked within a constant expression. A constexpr function must satisfy the following conditions: It is not virtual. Its return type is a literal type. Each of its parameters must be of a literal type.
The easiest way to check whether a function (e.g., foo ) is constexpr is to assign its return value to a constexpr as below: constexpr auto i = foo(); if the returned value is not constexpr compilation will fail.
Trivial default initialisation was prohibited in a constexpr
context until C++20.
The reason, I'm guessing, is that it is easy to "accidentally" read from default-initialised primitives, an act which gives your program undefined behaviour, and expressions with undefined behaviour are straight-up prohibited from being constexpr
(ref). The language has been extended though so that now a compiler must check whether such a read takes place and, if it doesn't, the default-initialisation should be accepted. It's a bit more work for the compiler, but (as you've seen!) has substantial benefits for the programmer.
This paper proposes permitting default initialization for trivially default constructible types in constexpr contexts while continuing to disallow the invocation of undefined behavior. In short, so long as uninitialized values are not read from, such states should be permitted in constexpr in both heap and stack allocated scenarios.
Since C++20, it's legal to leave v_
"uninitialised" like you have. Then you've gone on to assign all its elements values, which is great.
While this does not directly answer your question, I thought it was at least worth mentioning. You could simply use in-class initialization and zero-initialize the array:
int v_[N]{};
Another way, without initializing the array first, is to (privately) inherit from std::array
. Oddly enough, this actually gets accepted by GCC but not by Clang:
#include <array>
template<int N>
struct foo : private std::array<int, N> {
constexpr foo() {
for (auto i = int{}; i < N; ++i) {
(*this)[i] = i;
}
}
};
constexpr foo<5> bar;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With