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.