...
Do not write a syntactically semantically ambiguous 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
. . The syntax used in this example defines the former instead of the latter.
Code Block | ||||
---|---|---|---|---|
| ||||
#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
compliant solution shows two equally-compliant ways to write the declaration. The first way is to elide the parenthesis after the variable declaration; this ensures the syntax is that of a variable declaration instead of a function declaration. The second way is to use a braced-init-list to direct-initialize the local variable.
Code Block | ||||
---|---|---|---|---|
| ||||
class#include <iostream> struct Widget { public: explicit Widget() {cerr std::cout << "constructedConstructed" << std::endl; } }; intvoid mainf() { Widget w;w1; // Elide the parenthesis returnWidget 0;w2{}; // Use direct initialization } |
Running this program produces the single output constructed
: Constructed
.
...
Noncompliant 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
.This noncompliant code example demonstrates a vexing parse. The declaration Gadget g(Widget(i));
is not parsed as declaring a Gadget
object with a single argument, but is instead parsed as a function declaration with a redundant set of parenthesis around a parameter.
Code Block | ||||
---|---|---|---|---|
| ||||
class#include <iostream> struct Widget { public: explicit Widget(int inI) : i(in) {cerr { std::cout << "widgetWidget constructed" << std::endl; } private: int i}; }; classstruct Gadget { public: explicit Gadget(Widget wid) { : w(wid) {cerr std::cout << "gadgetGadget constructed" << std::endl; } private: Widget w}; }; intvoid mainf() { int i = 3; Gadget g(Widget(i)); std::cout << i << endl; return 0std::endl; } |
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 Parentheses around parameter names are optional, so the following is a semantically identical spelling of the declaration:
Code Block | ||||
---|---|---|---|---|
| ||||
Gadget g(Widget i); |
As a result, this program compiles cleanly is well-formed and prints only 3
as output, because no Gadget
or Widget
is objects are constructed.
Compliant Solution
This situation is ameliorated by moving the Widget
construction outside Gadget
compliant solution demonstrates two equally-compliant ways to write the declaration of g
. The first declaration, g1
, uses an extra set of parenthesis around the argument to the constructor call, forcing the compiler to parse this as a local variable declaration of type Gadget
instead of as a function declaration. The second declaration, g2
, uses direct initialization to similar effect.
Code Block | ||||
---|---|---|---|---|
| ||||
int main#include <iostream> struct Widget { explicit Widget(int I) { std::cout << "Widget constructed" << std::endl; } }; struct Gadget { explicit Gadget(Widget wid) { std::cout << "Gadget constructed" << std::endl; } }; void f() { int i = 3; Gadget g1((Widget w(i))); // Use extra parenthesis Gadget gg2{Widget(wi)}; // Use direct initialization std::cout << i << endl; return 0std::endl; } |
Running this program produces the expected output:
widget Widget constructed
Gadget constructed
Widget constructed
gadget Gadget constructed
3
Risk Assessment
Not guarding implicit constructor parsing could Syntactically ambiguous declarations can lead to unexpected behaviorprogram execution. However, it is likely that rudimentary testing would uncover violations of this rule.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
OOP31DCL34-CPP | lowLow | likelyUnlikely | lowMedium | P9P2 | L2L3 |
Automated Detection
Tool | Version | Checker | Description | ||||||
| 2510 |
Related Vulnerabilities
Search for other vulnerabilities resulting from the violation of this rule on the CERT website.
Related Guidelines
|
Bibliography
[ISO/IEC 14882-2014] | 6.8, "Ambiguity resolution" 8.2, "Ambiguity resolution" |
[Meyers 01] | Item 6 |
...
, "Be alert for C++'s most vexing parse |
...
" |
...