Original Question
I would like to use static member variables in order to pass information via a type template parameter into templated classes. These variables should not be set in a header file that is included in all translation units so that I can change them without recompiling most of the object files. Moreover, it would be nice to have a handy alias for the variable that does not require additional space. I thought that constexpr
read-only references like
static constexpr const int& alias = T::static_variable_name;
could serve as such an alias, but was not sure if that is valid. One restriction for constexpr
reads
- the constructor parameters or the value to be assigned must contain only literal values, constexpr variables and functions.
So I tried it using g++ -std=c++11
and got kind of inconsistent behavior for my taste. The code is included at the end of this question. In most of the cases, the code compiles and runs fine, but when using h2g2_oracle::answer
directly in the non-specialized class template (see comment "ERROR; WHY?"), compilation fails with the message
src/main.cpp:18:57: error: the value of ‘h2g2_oracle::_answer’ is not usable in a constant expression static constexpr const int& answer = h2g2_oracle::answer; // ERROR; WHY? src/main.cpp:11:9: note: ‘int h2g2_oracle::_answer’ is not const int h2g2_oracle::_answer = 42;
Why do most of the constexpr
references work as an alias and this single one does not?
#include <iostream>
// a specific kind of oracle
class h2g2_oracle {
protected:
static int _answer;
public:
static constexpr const int& answer = _answer; // public alias for reading
};
int h2g2_oracle::_answer = 42;
// some class template using a specific kind of oracle
template<typename oracle>
struct forecast {
// try to define an own alias
static constexpr const int& answer = oracle::answer; // works
//static constexpr const int& answer = h2g2_oracle::answer; // ERROR; WHY?
};
// specialized version for the h2g2_oracle
template<>
struct forecast<h2g2_oracle> {
// also define the own alias
static constexpr const int& answer = h2g2_oracle::answer; // works
};
int main() {
static constexpr const int& answer = h2g2_oracle::answer; // works
std::cout << answer << std::endl;
std::cout << forecast<h2g2_oracle>::answer << std::endl;
return 0;
}
Addressing the comments
@Ben Voigt concerning the gain of constexpr
: Yes, dyp is right with the assumption that I like the constexpr
version for its initialization inside the body. Long comment: I was very sure that an initialization in a separate translation unit would force the program to use some memory location where the address of the original value is stored. Therefor, I thought constexpr
might be useful to always have the static member initialization in the header, i.e., for templates (forecast<...>::answer
) and non-templates (h2g2_oracle::answer
). I know that I could change the non-template class to include a dummy template parameter so that initialization could be done in the header as well, but still the solution with constexpr
initially felt easier to me.
@dyp concerning a possible g++ bug: Ah, I did not expect to find one with such a "simple" test. Now that I see the related questions, that seems like a good explanation to me. Any practicable way for me to confirm/report/help? If you are quite confident with that, I could accept that as an answer at least.
@dyp concerning definition before using: Yes, I checked that g++ is fine with using oracle::answer
in the non-specialized forecast
even if the definition of h2g2_oracle
comes after the definition of forecast
. However, h2g2_oracle
has to be complete when forecast<h2g2_oracle>::answer
is used in the main
. This looks reasonable to me. Additional thought: What I am more fascinated about is the way how static members can be initialized as a reference to another static member (possibly in a different translation unit).
@dyp concerning
Interestingly, I'm not sure if you need a definition for h2g2::answer before using it in forecast; it should be odr-used (which I find strange).
I think I got your point now. This is also what I find fascinating: In fact, the int h2g2_oracle::_answer = 42;
can even be moved to a different translation unit and it still works. Somehow, the linker (im simple words) manages to "insert" the correct memory address of h2g2_oracle::_answer
where it is needed.