8

This code, with a const A& a member in B, where A has a deleted copy constructor, doesn't compile in GCC 4.8.1, but it works OK in clang 3.4:

class A {
public:
    A() = default;
    A(const A&) = delete;
    A& operator=(const A&) = delete;
};

class B{
public:
    B(const A& a)
        : a{a}
    { }
private:
    const A& a;
};

int main()
{
    A a{};
    B b{a};
}

Which one of the compilers is right?

The error in GCC is:

prog.cpp: In constructor ‘B::B(const A&)’:
prog.cpp:11:14: error: use of deleted function ‘A::A(const A&)’
        : a{a}
            ^
prog.cpp:4:5: error: declared here
    A(const A&) = delete;
    ^

Ideone: http://ideone.com/x1CVwx

M.M
  • 138,810
  • 21
  • 208
  • 365
smancill
  • 744
  • 1
  • 6
  • 13
  • What's Clang 5.1? The latest version of Clang is 3.5. – chris Jul 17 '14 at 03:50
  • In Mavericks I get this `Apple LLVM version 5.1 (clang-503.0.40) (based on LLVM 3.4svn)`, that's why I put 5.1, but I wasn't sure. – smancill Jul 17 '14 at 03:51
  • No wonder it works, compiler is from the future OOOoooOOooooooOO – Fantastic Mr Fox Jul 17 '14 at 03:51
  • The other warning GCC gives might be useful for its thought process: *warning: a temporary bound to 'B::a' only persists until the constructor exits* (`: a{a} {`) – chris Jul 17 '14 at 03:53
  • To fix change to `a(a)` ; the `{ }` form is requiring a temporary – M.M Jul 17 '14 at 03:54
  • NB. You don't need `B` here, simpler example: `A const &z {a};` – M.M Jul 17 '14 at 03:56
  • @MattMcNabb, Why should it create an initializer list rather than call the constructor? – chris Jul 17 '14 at 03:56
  • @chris, what constructor? References don't have constructors – M.M Jul 17 '14 at 03:57
  • @MattMcNabb, Err, sorry, I was thinking of `A`, but same thing as `int i{5};` then. I see what you're getting at, though. Even with list-initialization, it's still an initializer list. – chris Jul 17 '14 at 03:57
  • Works with gcc 4.9.0, looks like a bug fixed http://coliru.stacked-crooked.com/a/9ad52924a9c68ebc – quantdev Jul 17 '14 at 04:01
  • Ahh, *Otherwise, if T is a reference type, a prvalue temporary of the type referenced by T is list-initialized, and the reference is bound to that temporary. [ Note: As usual, the binding will fail and the program is ill-formed if the reference type is an lvalue reference to a non-const type. — end note ]* – chris Jul 17 '14 at 04:01
  • @Joachim sorry, I said "initializer list" rather than "list initialization". (The standard does not use the term "uniform initialization"). The `{` ... `}` is called the *initializer list* even if it is not required to construct a `std::initializer_list` – M.M Jul 17 '14 at 04:06
  • Duplicate of [this one](http://stackoverflow.com/questions/10509603/why-cant-i-initialize-a-reference-in-an-initializer-list-with-uniform-initializ?lq=1) although I don't want to close as Praetorian's answer here is good – M.M Nov 11 '14 at 05:44

1 Answers1

9

Your example can be reduced to

class A {
public:
    A() = default;
    A(const A&) = delete;
    A& operator=(const A&) = delete;
};

int main()
{
  A a{};
  A const& ar1(a); 
  A const& ar2{a}; // fails on gcc 4.8
}

The initialization of ar2 fails on gcc-4.8 with the error

error: use of deleted function ‘A::A(const A&)’

It compiles cleanly on clang3.4 and gcc4.9. This is the result of the resolution to CWG issue 1288.

N3337 contains the following language for list-initialization:

§8.5.4/3 [dcl.init.list]

List-initialization of an object or reference of type T is defined as follows:
...
— Otherwise, if T is a reference type, a prvalue temporary of the type referenced by T is list-initialized, and the reference is bound to that temporary

This, of course, means that the initialization of ar2 requires an accessible copy-constructor, hence the error.


The language has changed in N3797, where the initialization from an initializer list containing a single element takes precedence over the case quoted above.

— Otherwise, if the initializer list has a single element of type E and either T is not a reference type or its referenced type is reference-related to E, the object or reference is initialized from that element; ...
— Otherwise, if T is a reference type, a prvalue temporary of the type referenced by T is copy-list-initialized or direct-list-initialized, depending on the kind of initialization for the reference, and the reference is bound to that temporary.

So gcc 4.9 and clang 3.4 are implementing the resolution of issue 1288, while gcc 4.8 is following the wording in the C++11 standard.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • The [gcc bug report](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=50025) confirming this change was made for gcc4.9 – Praetorian Jul 17 '14 at 04:34
  • So, if the code have to support C++11, it should use `ar2(a)`, right? Is the resolution of issue 1288 considered part of C++11 or C++14? – smancill Jul 17 '14 at 04:48
  • @smancill Good question. Interestingly, since this situation is not a spot where copy-elision is permitted; if the copy-constructor is not deleted and it has side-effects then this "bug fix" would be a breaking change. – M.M Jul 17 '14 at 04:52
  • @smancill Yes, using parentheses instead of braces solves the problem. And I don't really know the answer to your second question. Clearly the resolution was added after the C++11 standard, but you get the fix for free with `-std=c++11` if you upgrade to gcc4.9 – Praetorian Jul 17 '14 at 04:52
  • 2
    @MattMcNabb Side effects in copy constructors are the real bug :P – Praetorian Jul 17 '14 at 04:53