Search code examples
c++classconstructorextern

extern const default constructed object


Here is my code

#include <iostream>
using namespace std;

class Q
{
public:
    Q() { cout << "constructor" << endl; }
};

extern const Q t/*()*/; //A
const Q s/*()*/; //B

int main()
{
    const Q t/*()*/;
}

I expect the line marked "A" to mean that I am creating an object t of type Q whose linkage is external, and whose fields can't be modified. The creation is done by the constructor with no arguments, which I have provided.

In main, I expect that a local t object of type Q is created in the same way, though its linkage will necessarily be just this file, and in fact, its scope and duration will just be for main.

C++ allows me to put or not put the parenthesis in const Q t/()/; in main. In global scope, in line A I can put or not put the parentheses, and the constructor will not be called either way.

In line B, I am only allowed to not put the parenthesis since otherwise, the compiler would be confused if I am defining a function prototype.

My questions are:

  1. Why am I allowed the flexibility to put the () or not in line //A, considering that in line //B this flexibility does not exist?

  2. Regardless of my choice in 1., I find that "constructor" is not printed by line A, even though it is printed in line B. Why? I don't really have an expectation on this. On the one hand, it would be reasonable to print it since we see in main that C++ understands to call the 0-argument constructor even without the parentheses. On the other hand, if that were the case, then how would I ever do a non-defining declaration of a class type?


Solution

  • An object declaration with extern keyword is not a definition, unless it includes an explicit initializer. Your attempt to use () in order to force a definition is a step in the right direction. However, it fails because such declaration with () produces a syntactic ambiguity, which is resolved in favor of function declaration, not object declaration.

    This applies equally to all of your declarations, not just to B, as you seem to believe. Why do you claim that "this flexibility does not exist" for line B is not clear to me: you can include () in line B or omit it. In either case the declaration will be perfectly valid, except that with () it will declare a function. (I.e. it is not a mere "flexibility", but rather a very drastic qualitative change.)

    For line B the problem is really non-existent, since line B does not use extern keyword. Without extern line B is an object definition regardless of whether you use an explicit initializer or not. And in your case leaving it as is (without ()) still triggers default construction, just like you wanted it. The same applies to the local declaration inside main.

    Line A, on the other hand, is problematic since in order to force a definition you need an explicit initializer, but that () does not do what you want it do to.

    In order to work around this problem in "classic" C++98 you would be forced to use

    extern const Q t = Q();
    

    syntax to make sure you are creating an object definition initialized by default constructor. (Pedantically, it is actually default construction followed by copy construction, but that's something we had to live with in C++98).

    In modern C++ (C++11 and later) you can make it into a definition (with default construction) by using {} initializer

    extern const Q t{};
    

    or

    extern const Q t = {};
    

    The {}-based syntax for obvious reasons does not produce any ambiguities with function declarations.