It is possible to devise syntax which can ambiguously be interpreted as either an expression statement or a declaration. Syntax of this sort is referred to as a vexing parse because the compiler must use disambiguation rules to determine the semantic results. The C++ Standard, [stmt.ambig], paragraph 1, states in part [ISO/IEC 14882-2014]:
There is an ambiguity in the grammar involving expression-statements and declarations: An expression-statement with a function-style explicit type conversion as its leftmost subexpression can be indistinguishable from a declaration where the first declarator starts with a
(
. In those cases the statement is a declaration. [Note: To disambiguate, the whole statement might have to be examined to determine if it is an expression-statement or a declaration. ...
A similarly vexing parse exists within the context of a declaration where syntax can be ambiguously interpreted as either a function declaration, or a declaration with a function-style cast as the initializer. The C++ Standard, [dcl.ambig.res], paragraph 1, states in part:
The ambiguity arising from the similarity between a function-style cast and a declaration mentioned in 6.8 can also occur in the context of a declaration. In that context, the choice is between a function declaration with a redundant set of parentheses around a parameter name and an object declaration with a function-style cast as the initializer. Just as for the ambiguities mentioned in 6.8, the resolution is to consider any construct that could possibly be a declaration a declaration.
Do not write a syntactically semantically ambiguous declaration, including vexing parses. With the advent of uniform initialization syntax using a braced-init-list, there is now syntax that unambiguously specifies a declaration instead of an expression statement. Declarations can also be disambiguated by using nonfunction-style casts, initialization using =
, or by removing extraneous parenthesis around the parameter name.
Noncompliant Code Example
In this noncompliant code example, an attempt is made to declare a local variable, w
, of type Widget
while executing the default constructor. However, this is syntactically ambiguous where the code could either be a declaration of a function pointer accepting no arguments and returning a Widget
, or a declaration of a local variable of type Widget
.
#include <iostream> struct Widget { Widget() { std::cout << "Constructed" << std::endl;} }; void f() { Widget w(); }
However, while a human may consider w
to be explicitly built with the default constructor, the compiler interprets w
to be a pointer to a function that takes no arguments, and returns a Widget
!
As a result, this program compiles and prints no output, because the default constructor is never actually invoked.
Compliant Solution
This situation is ameliorated by removing the parentheses after w
.
class Widget { public: explicit Widget() {cerr << "constructed" << endl;} }; int main() { Widget w; return 0; }
Running this program produces the single output constructed
.
Non-Compliant Code Example
Here is a more complex non-compliant example. The class Widget
maintains a single int
, and the class Gadget
maintains a single Widget
.
class Widget { public: explicit Widget(int in) : i(in) {cerr << "widget constructed" << endl;} private: int i; }; class Gadget { public: explicit Gadget(Widget wid) : w(wid) {cerr << "gadget constructed" << endl;} private: Widget w; }; int main() { int i = 3; Gadget g(Widget(i)); cout << i << endl; return 0; }
The declaration of g
is not parsed as a Gadget
with a 1-argument constructor. It is instead parsed as a pointer to a function that takes a single Widget
argument, called i
, and returns a Gadget
. For illustrative purposes, keep in mind that in a function declaration, parentheses around argument names are optional. So the following is a legitimate function declaration, and indicates how the compiler sees the above declaration:
Gadget g(Widget i);
As a result, this program compiles cleanly and prints only 3
as output, because no Gadget
or Widget
is constructed.
Compliant Solution
This situation is ameliorated by moving the Widget
construction outside Gadget
.
int main() { int i = 3; Widget w(i); Gadget g(w); cout << i << endl; return 0; }
Running this program produces the expected output:
widget constructed
gadget constructed
3
Risk Assessment
Not guarding implicit constructor parsing could lead to unexpected behavior.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
OOP31-CPP | low | likely | low | P9 | L2 |
Automated Detection
Tool | Version | Checker | Description |
4.4 | 2510 |
Bibliography
- [Meyers 01] Item 6: Be alert for C++'s most vexing parse.