Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"temporary of type 'A' has protected destructor", but its type is B

In the following code, compiled with Clang 8.0.0+ and -std=c++17, creating a derived class instance using B{} gives an error error: temporary of type 'A' has protected destructor. Why is A appearing in this message when the temporary has type B (and thus should have a public destructor)?

https://godbolt.org/z/uOzwYa

class A {
protected:
    A() = default;
    ~A() = default;
};

class B : public A {
// can also omit these 3 lines with the same result
public:
    B() = default;
    ~B() = default;
};

void foo(const B&) {}

int main() {

    // error: temporary of type 'A' has protected destructor
    foo(B{});
    //    ^

    return 0;
}
like image 669
jtbandes Avatar asked Jun 25 '19 00:06

jtbandes


1 Answers

This is a subtle issue of aggregate initialization before C++20.

Before C++20, B (and A) are aggregate types:

(emphasis mine)

no user-provided, inherited, or explicit constructors (explicitly defaulted or deleted constructors are allowed) (since C++17) (until C++20)

Then

If the number of initializer clauses is less than the number of members and bases (since C++17) or initializer list is completely empty, the remaining members and bases (since C++17) are initialized by their default member initializers, if provided in the class definition, and otherwise (since C++14) by empty lists, in accordance with the usual list-initialization rules (which performs value-initialization for non-class types and non-aggregate classes with default constructors, and aggregate initialization for aggregates).

So B{} constructs a temporary object via aggregate initialization, which will initialize the base subobject directly with empty list, i.e. perform aggregate initialization to construct the A base subobject. Note that the constructor of B is bypassed. The problem is that in such context, the protected desctructor can't be called to destroy the directly constructed base subobject of type A. (It doesn't complain about the protected constructor because it's bypassed by the aggregate initialization of A too.)

You can change it to foo(B()); to avoid aggregate initialization; B() performs value-initialization, the temporary object will be initialized by B's constructor, then anything is fine.

BTW since C++20 you code will work fine.

no user-declared or inherited constructors (since C++20)

B (and A) are not aggregate types again. B{} performes list initialization, and then the temporary object is initialized by B's constructor; the effect is just same as B().

like image 59
songyuanyao Avatar answered Sep 21 '22 23:09

songyuanyao