Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Assign static constexpr class member to runtime variable

I know there are a lot of similar questions, but somehow different questions. It is about the following situation:

#include <iostream>
#include <array>

template<typename T> class MyClass
{
public:
    static constexpr std::array<T,4> ARRAY {{4, 3, 1, 5}};
};

int main()
{
    constexpr std::array<int, 4> my_array(MyClass<int>::ARRAY); // works fine -> can use the ARRAY to initialize constexpr std::array

    constexpr int VALUE = 5*MyClass<int>::ARRAY[0]; // works also fine

    int value;
    value = my_array[0]; // can assign from constexpr
    value = MyClass<int>::ARRAY[0]; // undefined reference to `MyClass<int>::ARRAY

    std::cout << VALUE << std::endl;
    std::cout << value << std::endl;

    return 0;
}

As far as I understand constexpr is for compile-time constants. So the compiler can do already some calculation, for example to calculate the VALUE. Also I can obviously define a constexpr std::array<,>, from which I can assign the values to runtime variables. I would expect the compiler to set already value = 4 into the executable program, to avoid a loading operation. However, I cannot assign directly from the static member, getting the error

undefined reference to `MyClass<int>::ARRAY'
clang-3.7: error: linker command failed with exit code 1

which makes no sense to me, because it can be done with an intermediate step of another constexpr variable.

So my question is: Why can a static constexpr member of a class not be assigned to a runtime variable?

Note: In my MWE the class is a template class, which does not affect the error. However, I was originally interested in this particular case, which I expect to be more general as for a non-template class.

(Compiler is clang++ or g++ with -std=c++11 - they give the same error)

Edit: @Bryan Chen: Forgot the output lines. Are added now.

like image 523
marlam Avatar asked Mar 10 '16 21:03

marlam


People also ask

Can a constexpr be static?

A static constexpr variable has to be set at compilation, because its lifetime is the the whole program. Without the static keyword, the compiler isn't bound to set the value at compilation, and could decide to set it later.

Should constexpr always be static?

The short answer is that not only is static useful, it is pretty well always going to be desired. First, note that static and constexpr are completely independent of each other. static defines the object's lifetime during execution; constexpr specifies that the object should be available during compilation.

Can constexpr variable be changed?

Both const and constexpr mean that their values can't be changed after their initialization. So for example: const int x1=10; constexpr int x2=10; x1=20; // ERROR. Variable 'x1' can't be changed.

Can a member function be constexpr?

const can only be used with non-static member functions whereas constexpr can be used with member and non-member functions, even with constructors but with condition that argument and return type must be of literal types.


2 Answers

The undefined reference is a linker error. The rule is that if a variable is odr-used then it must have a definition. This applies even for constexpr variables.

Like most ODR rules, violating it is undefined behaviour with no diagnostic required (which could explain why you saw no diagnostic for some of your uses of the value).

To fix the error, add a definition outside the class:

template<typename T> constexpr std::array<T,4> MyClass<T>::ARRAY;

Since it is a template you can actually put this in the header, as opposed to the usual case where the definition goes in exactly one .cpp file.


The main issue here is whether ARRAY[0] counts as odr-use. According to this detailed post, in C++11 and C++14, indexing an array does count as odr-use , but this was changed by DR 1926 filed against C++14 to not be odr-use.

However, that is talking about builtin arrays. IDK whether the same rationale applies to std::array, I find the text of [basic.def.odr]/3 hard to understand. According to the informal definition on cppreference, std::array::operator[] would cause odr-use of the array because its return value binds a reference to the array.

like image 78
M.M Avatar answered Nov 06 '22 17:11

M.M


For this reason I always return constexpr objects from a constexpr function.

Modified code below. Note that due to a c++14 deficiency in std::array<> you must return a const std::array in order to allow operator[] to work.

#include <iostream>

#include <iostream>
#include <array>

template<typename T> class MyClass
{
public:
    static constexpr const std::array<T,4> ARRAY() { return {4, 3, 1, 5}; };
};

int main()
{
    constexpr std::array<int, 4> my_array(MyClass<int>::ARRAY()); // works fine -> can use the ARRAY to initialize constexpr std::array

    constexpr int VALUE = 5 * MyClass<int>::ARRAY()[0]; // works also fine

    int value;
    value = my_array[0]; // can assign from constexpr
    value = MyClass<int>::ARRAY()[0]; // undefined reference to `MyClass<int>::ARRAY

    std::cout << VALUE << std::endl;
    std::cout << value << std::endl;

    return 0;
}

expected results:

20
4
like image 39
Richard Hodges Avatar answered Nov 06 '22 17:11

Richard Hodges