14
votes

Say I have a class that that can return a constant expression through a constexpr function:

template<int N>
struct Foo {
  constexpr int Bar() const { return N; }
};

If I wanted to initialize constexpr values from Foo::Bar(), how should I pass a parameter of type Foo? I've tried these two, with an example constexpr variable inside of each to test that it can be initialized:

template<int N>
constexpr int ByValue(Foo<N> f) {
  constexpr int i = f.Bar();
  return f.Bar();
}

template<int N>
constexpr int ByReference(const Foo<N> &f) {
  constexpr int i = f.Bar();
  return f.Bar();
}

constexpr int a = ByValue(Foo<1>{});
constexpr int b = ByReference(Foo<1>{});

But clang 3.7 raises an error on ByReference while gcc >=5.1 does not: Live demo

main.cpp:15:25: error: constexpr variable 'i' must be initialized by a constant expression
      constexpr int i = f.Bar();
                        ^~~~~~~
main.cpp:22:25: note: in instantiation of function template specialization 'ByReference<1>' requested here
      constexpr int b = ByReference(Foo<1>{});

What's the difference between taking a const Foo & or a plain Foo, when Bar is constexpr either way and returns a valid constant expression?

Which is right and why, GCC or Clang? If available, references to the standard would be appreciated.

1
There is some silly language about references in the standard's enumeration of what can give you a compile time constant expression. Evidently clang pedantically honors that language, while g++ more practically disregards it. Hopefully it will be fixed, but I can't recall seeing any defect report about it. Upshot: this is gray zone, and may continue to be one. Use macros to define general array size function, just to be on the safe side.Cheers and hth. - Alf
You need to make your member function static, so that it doesn't depend on an implicit this pointer and maybe disqualify itself from being a constant expression.RamblingMad
I don't have the time to give an answer now but this is similar to this questionShafik Yaghmour
I added a section about the array size problem to the FAQ on arrays, now 5.4. Note to @Columbo, due to his argumentation here (now removed): this section of the FAQ was originally written by me. You can learn all about the dangers of this and that. ;-)Cheers and hth. - Alf

1 Answers

9
votes

§5.20:

enter image description here

The reference does not have a preceding initialization from the point of view of i, though: It's a parameter. It's initialized once ByReference is called.

Let's remove the constexpr from i's declaration and consider an invocation of ByReference in its entirety:

template<int N>
constexpr int ByReference(const Foo<N> &f) {
    int i = f.Bar();
    return i;
}

constexpr int j = ByReference(Foo<0>());

This is fine, since f does have preceding initialization. The initializer of f is a constant expression as well, since the implicitly declared default constructor is constexpr in this case (§12.1/5).
Hence i is initialized by a constant expression and the invocation is a constant expression itself.