You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 17 Next »

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

 PRQA QA-C++

 4.4

2510

 

 

Bibliography

  • [Meyers 01] Item 6: Be alert for C++'s most vexing parse.

  • No labels