8

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.

M.M
  • 138,810
  • 21
  • 208
  • 365
marlam
  • 590
  • 5
  • 14
  • `clang++` have this issue: http://coliru.stacked-crooked.com/a/e9698f2bb249e509 . But `g++` works: http://coliru.stacked-crooked.com/a/5ef23fe29b0aaa28 . Clang bug? – Bryan Chen Mar 10 '16 at 21:48
  • VS2015 says this: `in-class initialization for type 'const std::array' is not yet implemented; static member will remain uninitialized at runtime but use in constant-expressions is supported`. You might be running into something similar. – Weak to Enuma Elish Mar 10 '16 at 21:48
  • @Bryan: I was unprecise. I have g++4.8.5 installed, which does not yet support C++14. But I expected this to work already for C++11!? – marlam Mar 10 '16 at 21:55
  • I don't know if `template` messes up this particular situation, or more recent standards, but if this were a normal `class` declaration, you would still need to *define* the `static` member, even if constant (unless it were an `int` or other such type), no? – Jim Buck Mar 10 '16 at 22:13
  • [Similar question](http://stackoverflow.com/questions/25954621/access-static-constexpr-member-variable-without-instantiation-c11) - which does not have a satisfactory answer posted – M.M Mar 11 '16 at 01:14
  • [Very similar question](http://stackoverflow.com/questions/33204020/can-a-c-class-contain-a-static-const-stdarray-initialized-inline-in-a-header/) – M.M Mar 11 '16 at 01:21

2 Answers2

7

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.

Community
  • 1
  • 1
M.M
  • 138,810
  • 21
  • 208
  • 365
  • Thx for the nice answer. Do I understand right, that the use at compile time for other constexpr objects is _not_ odr-use and therefore needs no definition? In other words: The Compiler can calculate `VALUE` using the `operator[]` because it does not need a reference to is (and therefore no definition), but at runtime `value` cannot be calculated because an adress is needed and so a definition is? – marlam Mar 11 '16 at 10:05
  • @marlam I think they are both odr-use, but it is just some detail of the compiler that I don't know about which makes it give the error in one case but not the other. – M.M Mar 11 '16 at 11:19
4

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
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • Thx! Do I understand it right, that the subscript operator would work for the compile time calculations but not at runtime? And is this a special deficiency of the `std::array` or does this situation appear more often (as you say, you also return constexpr objects from constexpr function). And is this most likely to be fixed or has it a reason for being as is? And finally: Isn't is a design flaw to get the array via a function? – marlam Mar 10 '16 at 22:18
  • @marlam it's being fixed in c++17. It's because std::array's operator[] is not constexpr in the mutable case (the interface was defined in c++11 and they forgot to update it in c++14 when constexpr objects were able to be mutable). So the subscript operator will currently work in any case for a non-constexpr array and will work for a const constexpr array. This deficiency is not common and people who write constexpr objects as of c++14 (like me) are aware that they can be mutable – Richard Hodges Mar 10 '16 at 22:21