9

I have a discrepancy between the behaviour of g++ 4.8.1 and clang++ 3.4.

I've got a class A, of literal type, that has an explicit constexpr conversion function to type enum class E.

Gcc allows me to initialize constexpr variables of type E from a constant expression of type A using the conversion function in some cases, but not when the variable is a static class member (e2 below)

Clang rejects the initialization in all contexts (e1, e2 and e3).

According to [over.match.conv]p1 use of an explicit conversion function is OK here

enum class E { e };
struct A { explicit constexpr operator const E() const noexcept { return E::e; } };

constexpr E e1{A{}};                      // Gcc: OK, Clang: Error
struct B { static constexpr E e2{A{}}; }; // Gcc: Error, Clang: Error
void f() { static constexpr E e3{A{}}; }  // Gcc: OK, Clang: Error

I see a similar pattern when converting to another literal class type instead of an enum type - g++ rejects the initialization of s1, clang rejects the initialization of s1, s2 and s3. I think these should be valid as well, as per [over.match.copy]p1.

struct S { constexpr S(){} constexpr S(const S&){}};
struct A { explicit constexpr operator S() const noexcept { return S(); } };

constexpr S s1{A{}};                      // Gcc: OK, Clang: Error
struct B { static constexpr S s2{A{}}; }; // Gcc: Error, Clang: Error
void f() { static constexpr S s3{A{}}; }  // Gcc: OK, Clang: Error

Which compiler, if either, is right?


Edit: A couple of interesting things to note:

  1. The results are different between clang-3.4 and clang-svn, see comments below.
  2. When using parens for the initialization instead of braces, there is still a difference between e2/s2 and e1/e3/s1/s3, see http://coliru.stacked-crooked.com/a/daca396a63425c6b. gcc and clang-svn agree, but I'm not convinced that rejecting e2 and s2 is correct.
je4d
  • 7,628
  • 32
  • 46
  • note that the first 3 examples compile correctly on [Clang 3.5 SVN](http://coliru.stacked-crooked.com/a/dd5bbd2f90fe7457) – TemplateRex Jan 07 '14 at 13:56
  • @TemplateRex interesting, thanks – je4d Jan 07 '14 at 14:01
  • also note that using the `auto si = S(A{});` initialization form, also works for all three cases: http://coliru.stacked-crooked.com/a/8f23cd452086ef40 I have no clue why the `S()` syntax works when the `S{}` won't. – TemplateRex Jan 07 '14 at 14:04
  • For `auto si = S(A{});` g++ also accepts all 3. For `auto ei = E{A{}}` , clang-3.5 SVN accepts all 3 and g++ rejects all 3. – je4d Jan 07 '14 at 14:14
  • @DyP true, but [that's a function declaration](http://en.wikipedia.org/wiki/Most_vexing_parse)! – je4d Jan 07 '14 at 14:47
  • @je4d ouch, true. (It's also forbidden as an in-class member-initializer IIRC.) – dyp Jan 07 '14 at 14:52
  • About e2/s2 with parentheses: the syntax just doesn't allow `T x(v);` initialization of class members. –  Jan 26 '14 at 15:11
  • @hvd thanks - I'd just reached the same conclusion about 30 seconds ago after digging through the grammar; _member-declarator_ can only have a _brace-or-equal-initializer_ rather that any _initializer_ – je4d Jan 26 '14 at 15:18

1 Answers1

3

Strangely enough, Clang appears to be correct in rejecting these.

The reason is that there is a bug in the C++11 standard in which {} doesn't work for copy constructors. This is why () constructors work, but {} constructors don't.

Bjarne Stroustrup says under the errata for his book that it's fixed in C++14

Edward
  • 6,964
  • 2
  • 29
  • 55
  • Actually, I'm not sure this is applicable, so I've removed my previous comment. I think Bjarne is referring to issue [1467](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1467), which only applies to aggregates. My example struct has user-defined default c'tor and copy c'tor to stop it being an aggregate. – je4d Jan 26 '14 at 15:50
  • @je4d: I'm not sure that it doesn't apply. It seems to me that [decl.init]p14 says that the init list in this case is construed as the creation of a temporary and then a type conversion. If you omit the word "explicit" from your code, I think it will work, which seems to indicate that even though it's done at compile-time, the semantics seem that it's a two-step process. – Edward Jan 26 '14 at 15:53
  • What document are you using? `[dcl.init]p14` from C++11 (N3290) doesn't seem applicable here. By my reading, [over.match.copy], bullet point 2, sentence 2 says that explicit conversions should be considered here. – je4d Jan 26 '14 at 16:05
  • @je4d: At the moment, I'm using N3337, so there may well be differences, but I think it was just my error. Seems to me that is a List-initialization of e2 per `[decl.init.list]p3`, and also a List-initialization of a temporary A which is value-initialized. Given that, `[over.match.copy]` should rule, and indeed substituting `e1` for `A{}` works. I'd overlooked the sentence you mentioned and agree with your interpretation so it's still a mystery... – Edward Jan 26 '14 at 20:34