Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does this code compile without errors in C++17? [duplicate]

Tags:

c++

c++17

I have deleted all the constructors, even then following code is working perfectly. How and why ?

class Ax
{    
    public:
    
    Ax() = delete;
    Ax(Ax const&)=delete;
    Ax(Ax&&)=delete;
    void operator=(Ax const&)=delete;
    void operator=(Ax&&)=delete;

    void print()
    {
        cout << "Hello \n";
    }
};

int main(int argc, char** argv) 
{           
    Ax{}.print();
    return 0;
}
like image 281
virus00x Avatar asked Sep 29 '20 07:09

virus00x


People also ask

What are compile time errors in C++?

This compiler error indicates something that must be fixed before the code can be compiled. All these errors are detected by compiler and thus are known as compile-time errors. Syntax of a basic construct is written wrong.

Why does my code not compile?

There was no code, so I cannot answer specifically. In general, a code usually fails to compile because there is an error in its "syntax" -- that is, the code does not conform to the very-rigid "grammar" that is permitted by the "Programming Language" in which it is written.

What is syntax error in C++ compiler?

Type of errors Syntax errors: Errors that occur when you violate the rules of writing C/C++ syntax are known as syntax errors. This compiler error indicates something that must be fixed before the code can be compiled. All these errors are detected by compiler and thus are known as compile-time errors.

What are the types of errors in C++?

Type of errors. Syntax errors: Errors that occur when you violate the rules of writing C/C++ syntax are known as syntax errors. This compiler error indicates something that must be fixed before the code can be compiled. All these errors are detected by compiler and thus are known as compile-time errors.


2 Answers

(For a thorough walk-through of this topic, see the blog article The fickle aggregate)


Aggregate initialization

Class Ax is an aggregate in C++11, C++14 and C++17, as it has no user-provided constructors, which means that Ax{} is aggregate initialization, bypassing any user-declared constructors, even deleted ones.

struct NonConstructible {
    NonConstructible() = delete;
    NonConstructible(const NonConstructible&) = delete;
    NonConstructible(NonConstructible&&) = delete;
};

int main() {
    //NonConstructible nc;  // error: call to deleted constructor

    // Aggregate initialization (and thus accepted) in
    // C++11, C++14 and C++17.
    // Rejected in C++20 (error: call to deleted constructor).
    NonConstructible nc{};
}

The definition of what is an aggregate class has changed through various standard versions (C++11 through C++20), and these rules can have somewhat surprising consequences. As of C++20, particularly due to the implementation of

  • P1008R1: Prohibit aggregates with user-declared constructors

most of the frequently surprising aggregate behaviour has been addressed, specifically by no longer allowing aggregates to have user-declared constructors, a stricter requirement for a class to be an aggregate than just prohibiting user-provided constructors.


User-provided or only user-declared explicitly-defaulted constructors

Note that providing an explicitly-defaulted (or deleted) definition out-of-line counts as a user-provided constructor, meaning that in the following example, B has a user-provided default constructor, whereas A does not:

struct A {
    A() = default; // not user-provided.
    int a;
};

struct B {
    B(); // user-provided.
    int b;
};

// Out of line definition: a user-provided
// explicitly-defaulted constructor.
B::B() = default;

with the result that A is an aggregate, whereas B is not. This, in turn, means that initialization of B by means of an empty direct-list-init will result in its data member b being left in an uninitialized state. For A, however, the same initialization syntax will result in (via aggregate initialization of the A object and subsequent value initalization of its data member a) zero-initialization of its data member a:

A a{};
// Empty brace direct-list-init:
// -> A has no user-provided constructor
// -> aggregate initialization
// -> data member 'a' is value-initialized
// -> data member 'a' is zero-initialized

B b{};
// Empty brace direct-list-init:
// -> B has a user-provided constructor
// -> value-initialization
// -> default-initialization
// -> the explicitly-defaulted constructor will
//    not initialize the data member 'b'
// -> data member 'b' is left in an unititialized state

This may come as a surprise, and with the obvious risk of reading the uninitialized data member b with the result of undefined behaviour:

A a{};
B b{};     // may appear as a sound and complete initialization of 'b'.
a.a = b.b; // reading uninitialized 'b.b': undefined behaviour.
like image 67
dfrib Avatar answered Oct 01 '22 02:10

dfrib


In C++17, your example is an aggregate. For C++17 aggregates only need to have no user-provided constructors; user-declared (but explicitly deleted or defaulted) constructors are fine.

In this case, then, aggregate initialization is performed when you do Ax{}, which doesn't call any of the constructors... including the deleted ones, and so this compiles.

In C++20 the rules were changed so that any user-declared constructors prevent the type from being an aggregate, and so the example will fail to compile.

See also https://en.cppreference.com/w/cpp/language/aggregate_initialization

like image 34
N. Shead Avatar answered Oct 01 '22 01:10

N. Shead