5

I learnt about Inheritance in C++. Then to check that I've understood the concept correctly, I have written the below given program that is rejected by clang but accepted by gcc and msvc. Live Demo

#include <array>
#include <iostream>
class Base
{
private: 
    int data;
public:
    Base(int pdata):data(pdata) {}
    Base(const Base&){std::cout <<" Copy base";}

};

class Derived : public Base
{ 
    
};
 
int main()
{
    Derived d(1); //rejected by clang but accepted by gcc and msvc
} 

I am using C++20 and want to know which compiler is correct here in C++20. I've also noticed that with C++17 all compilers reject this but from c++20 onwards, gcc and msvc start compiling the program. So it seems there was some change in the c++20 standard. But I don't know what that change is(assuming there is any such change) and whether or not the program is well formed in c++20.

The clang c++20 error says:

<source>:19:12: error: no matching conversion for functional-style cast from 'int' to 'Derived'
    Base d(Derived(1));
2
  • 3
    According to cppreference, Parenthesized initialization of aggregates is not supported by Clang yet. Commented Nov 27, 2022 at 13:18
  • Derived d(1) is aggregate initialization from c++20 and so the program is well-formed from c++20 onwards. See answer below. Commented Nov 27, 2022 at 13:25

3 Answers 3

1

This is a C++20 feature called parenthesized initialization of aggregates that clang has not yet implemented. That is, the program is well-formed from C++20 onwards. This can be seen from compiler support documentation:

C++20 feature Paper(s) GCC Clang MSVC
Parenthesized initialization of aggregates P0960R 10 19.28 (16.8)*

As we can see in the above table, the entry for clang is empty/blank meaning that this feature has not been implemented in clang as of now.


Now let's look at how exactly this program is well-formed in c++20.

From direct initialization:

T object ( arg );          (1) 

The effects of direct initialization are:

  • If T is a class type,
    • otherwise (from C++20), if the destination type is a (possibly cv-qualified) aggregate class, it is initialized as described in aggregate initialization except that narrowing conversions are permitted, designated initializers are not allowed, a temporary bound to a reference does not have its lifetime extended, there is no brace elision, and any elements without an initializer are value-initialized.

(emphasis mine)

This means that since Derived is an aggregate class, aggregate initialization will be performed for Derived d(1); from C++20.

Note also that Derived is an aggregate from C++17 onwards but Derived d(1); is aggregate initialization only from C++20 onwards due to P0960R.

Now from aggregate initialization:

The effects of aggregate initialization are: 2) Determine the explicitly initialized elements of the aggregate as follows:

  • Otherwise, if the initializer list is non-empty, the explicitly initialized elements of the aggregate are the first n elements of the aggregate, where n is the number of elements in the initializer list. 3) Initialize each element of the aggregate in the element order. That is, all value computations and side effects associated with a given element are sequenced before those of any element that follows it in order (since C++11).

(emphasis mine)

This means that the base class subobject of the derived object is the explicitly initialized element of the aggregate and it will be copy initialized as per Initializing elements:

For each explicitly initialized element:

  • Otherwise, the element is copy-initialized from the corresponding initializer clause of the initializer list:
    • If the initializer clause is an expression, implicit conversions are allowed as per copy-initialization, except that narrowing conversions are prohibited (since C++11).

(emphasis mine)

This means that the base subobject will be initialized as per the rules of copy initialization with the expression 1 as the initializer. Thus the program is well-formed from C++20 onwards and gcc and msvc are correct in accepting the program. OTOH, clang has not yet implemented this feature.

Sign up to request clarification or add additional context in comments.

Comments

1

Though the other answers have identified why it works in some compilers (because they support "parenthesized initialization of aggregates") it seems to me that this is not what you intend.

It looks to me like you expect the derived class to inherit the constructor from the base class. That does not happen in C++ though; constructors are not inherited. Or at least not inherited in such a way that they become constructors of the derived type.

To fix your program:

class Derived : public Base
{ 
public:
   Derived(int arg) : Base(arg) { } 
};

and, presto, now your code is good old C++98—almost! If we get rid of #include <array>, it compiles with g++ -std=c++98. Plus I think it has to be <iostream.h> in true C++98.

Anyway, without this constructor, the parenthesized initialization d(1) requires the C++20 support which performs the equivalent of this:

   Derived d = { 1 }

but you really don't want to be doing either of these. Which means that the C++20 feature is kind of a misfeature which has taken away a diagnostic, replacing it with behavior you don't want. You don't want a class which has constructors to be brace initialized; it violates encapsulation.

2 Comments

This is a language lawyered question.
@Alan "I learnt about Inheritance in C++ ..." is a language-lawyered question? Doesn't look like it to me. OP knowing about the existence of different C++ versions and how to request them from various compilers does give it that air.
0

The code is valid C++20, but not all C++20 features are supported by all compilers. You've run into a feature (parenthesized initialization) which Clang does not yet have.

This kind of thing happens with each new C++ standard. The support can seem nearly complete the year a standard is released, but with a few missing bits which can take several years to appear (if ever).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.