12

How is {} initialization in a constructor initialization list different from () initialization when initializing reference to abstract types? Take class Bar below:

class AbstractBase
{
public:
    AbstractBase() {}
    virtual ~AbstractBase() = default;

    virtual void ab() = 0;
};

class Foo : public AbstractBase
{
public:
    Foo() {}

    void ab() {}
};

class Bar
{
public:
    Bar(const AbstractBase& base) : myBase{base} {}

private:
    const AbstractBase& myBase;
};


int main()
{
    Foo f{};
    Bar b{f};

}

When compiling, I get the error

test5.cpp: In constructor ‘Bar::Bar(const AbstractBase&)’:
test5.cpp:22:48: error: cannot allocate an object of abstract type ‘AbstractBase’
     Bar(const AbstractBase& base) : myBase{base}
                                                ^
test5.cpp:2:7: note:   because the following virtual functions are pure within ‘AbstractBase’:
 class AbstractBase
       ^
test5.cpp:8:18: note:   virtual void AbstractBase::ab()
     virtual void ab() = 0;

Changing the line

Bar(const AbstractBase& base) : myBase(base) {}

it compiles and runs fine.

Reading through Stroustrup's C++11 book, I was under the impression that {} was the same as () in most all cases, except where there was an ambiguity between constructors that take std::initializer_list<> and other constructors, and cases where using auto as the type, neither of which I'm doing here.

M.M
  • 138,810
  • 21
  • 208
  • 365
oryan_dunn
  • 198
  • 1
  • 8
  • 1
    My rule of thumb: Use `{}` for lists of elements (including zero), and `()` to explicitly call any other constructor. – Mooing Duck Oct 29 '14 at 21:26
  • 1
    This is actually a problem to do with list initialization of references – M.M Oct 29 '14 at 21:26
  • Same issue as here - http://stackoverflow.com/questions/19347004/copy-constructor-curly-braces-initialization – Rudolfs Bundulis Oct 29 '14 at 21:28
  • @RudolfsBundulis thanks for that find, I was wondering why my code sample failed to even compile without `S() {}` present! – M.M Oct 29 '14 at 21:36
  • @MattMcNabb yeah, got the difference from your answer, didn't think it through deep enough – Rudolfs Bundulis Oct 29 '14 at 21:41
  • Even closer version of the same issue: http://stackoverflow.com/questions/24794598/initialization-of-const-reference-member-with-deleted-copy-constructor/24794857 – M.M Oct 29 '14 at 22:03

1 Answers1

13

Short answer: This was a bug in the Standard which is fixed in C++14, and g++ 4.9 has the fix (retroactively applied to C++11 mode too). Defect Report 1288


Here's a simpler example:

struct S
{
    int x;
    S() { }     // this causes S to not be an aggregate (otherwise aggregate 
                // initialization is used instead of list initialization)
};

S x = 5;
S const &y { x } ;    

x = 6;
std::cout << y << std::endl;     // output : 5

In the text of C++11, the meaning of S const &y {x}; is not to bind y to x; in fact the meaning is to create a temporary and bind a reference to that. From C++11 [dcl.init.ref]/3:

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 ]

This is pretty silly , clearly the intent of this code is to bind y directly to x. In C++14 the text was changed:

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;

Since a type is reference-related to itself (or one of its base classes), in my sample here and in your actual code, it should actually bind correctly.


Your error message comes from the compiler following the C++11 wording and attempting to create a temporary from base to bind the reference to; and this fails because base is of an abstract type.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • If I remove const from my reference when using {} initialization, I get a different error: `test5.cpp: In constructor ‘Bar::Bar(AbstractBase&)’: test5.cpp:22:42: error: invalid initialization of non-const reference of type ‘AbstractBase&’ from an rvalue of type ‘’ Bar(AbstractBase& base) : myBase{base} {} ^` – oryan_dunn Oct 29 '14 at 21:43
  • 3
    @oryan_dunn that's attempting to bind a temporary to a non-const reference – M.M Oct 29 '14 at 21:48
  • yeah, I realize that now, it's trying to make a temporary of an abstract type. I guess until C++14 comes along, I'll have to continue to use () on all my constructor initialization lists to remain consistent. – oryan_dunn Oct 29 '14 at 21:51
  • No, you don't need to wait for C++14, you can just use a newer compiler that implements DR1288. – Jonathan Wakely Oct 29 '14 at 22:17
  • I see that GCC 4.9 should fix it, but I'm stuck on 4.8.2 via RHEL 6.5 and devtoolset 2.1. – oryan_dunn Oct 29 '14 at 22:59