Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Legitimate to initialize an array in a constexpr constructor?

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_sequences.

like image 200
Yongwei Wu Avatar asked Jan 09 '20 10:01

Yongwei Wu


People also ask

How to initialize constexpr array?

Manual initialization A constexpr array can be declared and initialized manually through: constexpr int arr[3] = {1,2,3};

Can a constructor be constexpr?

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.

What is a constexpr function?

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.

How do I know if a function is constexpr?

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.


2 Answers

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.

like image 169
Lightness Races in Orbit Avatar answered Oct 16 '22 09:10

Lightness Races in Orbit


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;
like image 20
303 Avatar answered Oct 16 '22 08:10

303