Why won't this compile without a default constructor?

  • A+
Category:Languages

I can do this:

#include <iostream>  int counter;  int main() {     struct Boo     {         Boo(int num)         {             ++counter;             if (rand() % num < 7) Boo(8);         }     };      Boo(8);      return 0; } 

This will compile fine, my counter result is 21. However when I try to create the Boo object passing the constructor argument instead of an integer literal I get a compile error:

#include <iostream>  int counter;  int main() {     struct Boo     {         Boo(int num)         {             ++counter;             if (rand() % num < 7) Boo(num); // No default constructor                                              // exists for Boo         }     };      Boo(8);      return 0; } 

How is a default constructor called in the second example but not in the first example? This is error I get on Visual Studio 2017.

On the online C++ compiler onlineGDB I get the errors:

error: no matching function for call to ‘main()::Boo::Boo()’     if (rand() % num < 7) Boo(num);                             ^ note:   candidate expects 1 argument, 0 provided 

 


Clang gives this warning message:

<source>:12:16: warning: parentheses were disambiguated as redundant parentheses around declaration of variable named 'num' [-Wvexing-parse]             Boo(num); // No default constructor                 ^~~~~ 

This is a most-vexing parse issue. Because Boo is the name of a class type and num is not a type name, Boo(num); could be either the construction of a temporary of type Boo with num being argument to Boo's constructor or it could be a declaration Boo num; with extra parentheses around the declarator num (which declarators may always have). If both are valid interpretations, the standard requires the compiler to assume a declaration.

If it is parsed as declaration, then Boo num; would call the default constructor (the constructor without arguments), which isn't declared either by you or implicitly (because you declared a another constructor). Therefore the program is ill-formed.

This is not an issue with Boo(8);, because 8 cannot be a variable's identifier (declarator-id), so it is parsed as a call creating a Boo temporary with 8 as argument to the constructor, thereby not calling the default constructor (which is not declared), but the one you defined manually.

You can disambiguate this from a declaration by either using Boo{num}; instead of Boo(num); (because {} around the declarator is not allowed), by making the temporary a named variable, e.g. Boo temp(num);, or by putting it as an operand in another expression, e.g. (Boo(num));, (void)Boo(num);, etc.

Note that the declaration would be well-formed if the default constructor was usable, because it is inside the if's branch block scope rather than the function's block scope and would simply shadow the num in the function's parameter list.

In any case it doesn't seem a good idea to misuse temporary object creation for something that should be a normal (member) function call.

This particular type of most-vexing parse with a single non-type name in the parenthesis can only happen because the intend is to create a temporary and immediately discard it or alternatively if the intend is to create a temporary used directly as an initializer, e.g. Boo boo(Boo(num)); (actually declares function boo taking a parameter named num with type Boo and returning Boo).

Discarding temporaries immediately is usually not intended and the initializer case can be avoided using brace-initialization or double-parantheses (Boo boo{Boo(num)}, Boo boo(Boo{num}) or Boo boo((Boo(num)));, but not Boo boo(Boo((num)));).

If Boo wasn't a type name, it could not be a declaration and no problem occurs.

I also want to emphasize that Boo(8); is creating a new temporary of type Boo, even inside the class scope and constructor definition. It is not, as one might erroneously think, a call to the constructor with the caller's this pointer like for usual non-static member functions. It is not possible to call another constructor in this way inside the constructor body. This is only possible in the member initializer list of the constructor.


This happens even though the declaration would be ill-formed due to missing constructor, because of [stmt.ambig]/3:

The disambiguation is purely syntactic; that is, the meaning of the names occurring in such a statement, beyond whether they are type-names or not, is not generally used in or changed by the disambiguation.

[...]

Disambiguation precedes parsing, and a statement disambiguated as a declaration may be an ill-formed declaration.


Fixed in edit: I overlooked the declaration in question being in a different scope than the function parameter and the declaration therefore being well-formed if the constructor was available. This is not considered during disambiguation in any case. Also expanded on some details.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: