1

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.

Community
  • 1
  • 1
Julius
  • 1,816
  • 10
  • 14
  • It's not clear what you think `constexpr` is doing for you here, but I rather suspect it isn't helping you in any way. – Ben Voigt Sep 30 '14 at 13:32
  • @BenVoigt The `constexpr` allows a definition within the class body. – dyp Sep 30 '14 at 13:48
  • [clang++3.5 does not complain](http://coliru.stacked-crooked.com/a/34212c61d558c7a5) – dyp Sep 30 '14 at 13:50
  • I think this is a g++ bug. You may not "read" from e.g. `h2g2_oracle::_answer` or `forecast<..>::answer`, since that's an lvalue-to-rvalue conversion of a non-const object. But initializing a reference does not require lvalue-to-rvalue conversion, it merely requires an object with static storage duration. – dyp Sep 30 '14 at 13:54
  • 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). – dyp Sep 30 '14 at 13:58
  • Since g++ and clang++ disagree, you should check their respective latest releases/revisions (if possible, compile from repos). If those still disagree (http://melpon.org/wandbox/ says they do), you can file a bug report. This situation is an indicator for either a bug in one compiler or an unclear passage in the Standard. – dyp Sep 30 '14 at 22:56
  • *"even if the definition of h2g2_oracle comes after the definition of forecast"* That's not what I meant. You have a definition for `int h2g2_oracle::_answer = 42;` but none of the other static data members are defined (only declared). – dyp Sep 30 '14 at 22:59

1 Answers1

2

Simple workaround which is inline and certainly requires no storage:

template<typename oracle>
struct forecast
{
  static const int& answer() { return h2g2_oracle::answer; }
};

It is also C++98-compatible.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • True. And it actually feels funny to call this a workaround since this should be the traditional way to go. As an aesthetic aspect, I would prefer an alias which can be passed as `foo(answer)` instead of `foo( answer() )` but not if the complications I experienced are involved. – Julius Oct 01 '14 at 05:24
  • I am however tempted not to accept this as *the* answer because I am still interested in the answer to the original question. Please complain if you disagree. – Julius Oct 01 '14 at 05:25
  • @Julius: I perfectly understand that this might meet the "useful" criteria for an upvote but not be accepted (which marks the question resolved to your satisfaction). – Ben Voigt Oct 01 '14 at 12:47