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.
Different programming languages have various ways to delineate the start and end points of a programming structure, such as a loop, method or conditional statement. For example, Java and C++ are often referred to as curly brace languages because curly braces are used to define the start and end of a code block.
The constructors should be used to initialize member variables of the class because member variables cannot be declared or defined in a single statement. Therefore, constructors are used in initializing data members of a class when an object is created.
Initializer List is used in initializing the data members of a class. The list of members to be initialized is indicated with constructor as a comma-separated list followed by a colon. Following is an example that uses the initializer list to initialize x and y of Point class.
Braced initialization: a review Works in most contexts; No implicit copy construction (as opposed to = ); No more narrowing conversions: the compiler will emit an error in case of truncation instead of silently converting the value; No more most vexing parse!
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With