0

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?

R Sahu
  • 204,454
  • 14
  • 159
  • 270
Jeff
  • 125
  • 7
  • Ir's not entirely obvious what you are asking, but this `const Q s()` doesn't construct anything, it declares a function. Search here and elsewhere for "most vexing parse". –  Jan 27 '17 at 21:36
  • @NeilButterworth How can I clarify what I am asking? The parens are commented out. They wouldn't be allowed, as you and I both noted. – Jeff Jan 27 '17 at 21:41
  • They _would_ be allowed. When posting here _don't_ post commented out code - it simply makes it harder to understand what you are actually asking about, and requires us to edit the code if we want to compile it. –  Jan 27 '17 at 21:42
  • @NeilButterworth, I don't think the post marked as duplicate answers the OP's questions. – R Sahu Jan 27 '17 at 22:03
  • @NeilButterworth I don't see how your link answers the two questions I posed. You are correct that the parens can be put in line B. It is important that I have the commented out parens so that in the following, you know what I'm talking about. Here are my questions, rephrased. There are $2^3=8$ possible combinations of whether I omit or put the parentheses, and there is the question of whether the code compiles and what the output is. – Jeff Jan 27 '17 at 22:04
  • My observations are that the pair of parentheses in main has no effect on either question, the pair in line B does not affect whether or not the code compiles, but does affect the output (one fewer "constructor" in the output if the parens are there, as expected), and the pair in line A has no effect, which is somewhat surprising to me as I described in the OP. I expect it to affect if the code is legal, as described in 1. in the OP, because extern functions are a thing, and shouldn't the parsing issue come up in the same way? I also wonder why the constructor is not called parens or not. – Jeff Jan 27 '17 at 22:06
  • @R Sahu I have re-opened it, as I obviously don't understand what the OP is asking. I think it is fair to say it is about the MVP, though, which the OP seems to be completely unaware of, despite being directed to it. –  Jan 27 '17 at 22:06
  • My best guess at how this works is: non-defining declarations are allowed for only primitive types in a block, or any type with the extern storage modifier in global scope. This is why, inside main, my code would be problematic if I defined a constructor with parameters, but not one without parameters. The compiler wouldn't let me give a non-defining declaration of a class type. However, in global scope, line A with parens declares an extern function, without parens declares an extern Q object, without constructing it. line B with parens declares a function which is extern by default. – Jeff Jan 27 '17 at 22:31
  • Without it, it declares and constructs an object of type Q. So my question 1 was based on the wrong premise that I could not put the parens in line B; in either case, it is parsed as a function declaration, which is fine and completely parallel. But if lines A and B are compared without the parens in either, we find that line A is a declaration, while line B causes a constructor call, hence the situation is not parallel. I suspect that that no paren implies default construction for only non-extern, otherwise you could never invoke your right, for extern, to make a nondeclaring definition. – Jeff Jan 27 '17 at 22:40

1 Answers1

1

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.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765