14

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.

Xo Wang
  • 377
  • 1
  • 4
  • 9
  • 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 Jul 30 '15 at 04:19
  • 1
    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 Jul 30 '15 at 06:59
  • I don't have the time to give an answer now but this is similar to [this question](http://stackoverflow.com/q/31375381/1708801) – Shafik Yaghmour Jul 30 '15 at 09:27
  • I added a section about the array size problem to [the FAQ on arrays](http://stackoverflow.com/questions/4810664/how-do-i-use-arrays-in-c/7439261#7439261), 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 Jul 31 '15 at 00:04

1 Answers1

9

§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.

Columbo
  • 60,038
  • 8
  • 155
  • 203
  • 1
    @Walter No. Clang rejects it, thus it's right. By the current rules, that is. – Columbo Jul 30 '15 at 09:56
  • I am a fan of this screenshot / highlight approach to quoting. +1! – Barry Jul 30 '15 at 12:40
  • @Barry Yeah, I figured this would be more comprehensible than my attempts of creating tables in markdown ;-) – Columbo Jul 30 '15 at 12:43
  • @Columbo What's the difference between your example and OP's with regards to "preceding initialization"? They look the same to me as far as `f` is concerned. – Barry Jul 30 '15 at 12:45
  • If `i` inside the function is marked as `constexpr`, then the initializer must be a constant expression *itself*. But it isn't, because the reference used has not been initialized at that point. However, `ByReference(Foo<0>())` is a constant expression because the parameter has indeed been initialized when `i` is about to be initialized. A similar case is shown in the example section of §5.20/2 with `incr`. – Columbo Jul 30 '15 at 12:50
  • Please don't post pictures of text. Simply quote the text back in here. – Shoe Jul 30 '15 at 16:20
  • @ʎǝɹɟɟɟǝſ …and why exactly wouldn't I post pictures of text when I could never reach the nice formatting that the picture shows with markdown? – Columbo Jul 30 '15 at 16:27
  • 4
    @Columbo Because blind people also visit the website. – Shoe Jul 30 '15 at 16:35
  • "because the reference used has not been initialized at that point", what do you mean by "at that point"? Do you mean a time point when the program is executing, or a lexical point of the program? If you mean the former, the reference is indeed initialized at that point. If you mean the latter, why is that different for `ByReference(Foo<0>())` (while the lexical structure of the program is the same)? – xskxzr Aug 22 '18 at 03:59
  • @xskxzr Another way to see it is that you can't have a constexpr reference refer to different objects. In this case, because the reference is a parameter, it can be called with different objects – Rakete1111 Aug 23 '18 at 15:11
  • @Rakete1111 I know the rationality of the rule: when the compiler parses `int i = f.Bar();`, it does not know what `f` refers to, when the compiler parses `constexpr int j = ByReference(Foo<0>());`, it knows what `f` refers to. I'm just confused with the unclear wording "preceding" in the specification. – xskxzr Aug 23 '18 at 15:36
  • @xskxzr Ahh great. Yeah, I find it a bit weird too. – Rakete1111 Aug 23 '18 at 15:39
  • 1
    @Rakete1111 I have filed an [editorial issue](https://github.com/cplusplus/draft/issues/2307), and someone pointed out that this is already a [core language issue](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#2186). – xskxzr Aug 24 '18 at 08:14