It is possible to devise syntax that can ambiguously be interpreted as either an expression statement or a declaration. Syntax of this sort is called a vexing parse because the compiler must use disambiguation rules to determine the semantic results. The C++ Standard, [stmt.ambig], paragraph 1 [ISO/IEC 14882-2014], in part, states the following:

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, in part, states the following:

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 ambiguous declaration. 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, by initializing using =, or by removing extraneous parenthesis around the parameter name.

Noncompliant Code Example

In this noncompliant code example, an anonymous local variable of type std::unique_lock is expected to lock and unlock the mutex m by virtue of RAII. However, the declaration is syntactically ambiguous as it can be interpreted as declaring an anonymous object and calling its single-argument converting constructor or interpreted as declaring an object named m and default constructing it. The syntax used in this example defines the latter instead of the former, and so the mutex object is never locked.

#include <mutex>

static std::mutex m;
static int shared_resource;

void increment_by_42() {
  std::unique_lock<std::mutex>(m);
  shared_resource += 42;
}

Compliant Solution

In this compliant solution, the lock object is given an identifier (other than m) and the proper converting constructor is called.

#include <mutex>
 
static std::mutex m;
static int shared_resource;

void increment_by_42() {
  std::unique_lock<std::mutex> lock(m);
  shared_resource += 42;
}

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 declaration is syntactically ambiguous where the code could be either 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.

#include <iostream>
 
struct Widget {
  Widget() { std::cout << "Constructed" << std::endl; }
};

void f() {
  Widget w();
}

As a result, this program compiles and prints no output because the default constructor is never actually invoked.

Compliant Solution

This compliant solution shows two equally compliant ways to write the declaration. The first way is to elide the parentheses after the variable declaration, which 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.

#include <iostream>
 
struct Widget {
  Widget() { std::cout << "Constructed" << std::endl; }
};

void f() {
  Widget w1; // Elide the parentheses
  Widget w2{}; // Use direct initialization
}

Running this program produces the output Constructed twice, once for w1 and once for w2.

Noncompliant Code Example

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. It is instead parsed as a function declaration with a redundant set of parentheses around a parameter. 

#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 g(Widget(i));
  std::cout << i << std::endl;
}

Parentheses around parameter names are optional, so the following is a semantically identical spelling of the declaration.

Gadget g(Widget i);

As a result, this program is well-formed and prints only 3 as output because no Gadget or Widget objects are constructed.

Compliant Solution

This compliant solution demonstrates two equally compliant ways to write the declaration of g. The first declaration, g1, uses an extra set of parentheses around the argument to the constructor call, forcing the compiler to parse it as a local variable declaration of type Gadget instead of as a function declaration. The second declaration, g2, uses direct initialization to similar effect.

#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(i))); // Use extra parentheses
  Gadget g2{Widget(i)}; // Use direct initialization
  std::cout << i << std::endl;
}

Running this program produces the expected output.

Widget constructed
Gadget constructed
Widget constructed 
Gadget constructed
3

Risk Assessment

Syntactically ambiguous declarations can lead to unexpected program execution. However, it is likely that rudimentary testing would uncover violations of this rule.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

DCL53-CPP

Low

Unlikely

Medium

P2

L3

Automated Detection

Tool

Version

Checker

Description

CodeSonar
8.1p0

LANG.STRUCT.DECL.FNEST

Nested Function Declaration

Helix QAC

2024.4

C++1109, C++2510
Klocwork
2024.4
CERT.DCL.AMBIGUOUS_DECL
LDRA tool suite
9.7.1

 

296 S

Partially implemented

Parasoft C/C++test

2023.1

CERT_CPP-DCL53-a
CERT_CPP-DCL53-b
CERT_CPP-DCL53-c

Parameter names in function declarations should not be enclosed in parentheses
Local variable names in variable declarations should not be enclosed in parentheses
Avoid function declarations that are syntactically ambiguous

Polyspace Bug Finder

R2024a

CERT C++: DCL53-CPP

Checks for declarations that can be confused between:

  • Function and object declaration
  • Unnamed object or function parameter declaration

Rule fully covered.

Clang
3.9
-Wvexing-parse
SonarQube C/C++ Plugin
4.10
S3468

Related Vulnerabilities

Search for other vulnerabilities resulting from the violation of this rule on the CERT website.

Bibliography

[ISO/IEC 14882-2014]Subclause 6.8, "Ambiguity Resolution"
Subclause 8.2, "Ambiguity Resolution"
[Meyers 2001]Item 6, "Be Alert for C++'s Most Vexing Parse"




10 Comments

  1. Would two ctors taking similar types permit a violation of this rule?

    class Widget {
      Widget(int i) { std::cout << "Widget constructed from int" << std::endl; }
      Widget(char* p) { std::cout << "Widget constructed from ptr" << std::end; }
      // ...
    }
    // ...
    Widget w = 'p'; // Surprise, invokes int ctor, not ptr ctor! Violation?
    
    1. I don't think that's related to this rule. This rule seems to be about the Most Vexing Parse in particular, and not about other ways that trying to call a constructor could do the wrong thing.

      1. Well, my code example is related, in that it also describes surprising behavior wrt 1-element constructors. The code example doesn't violate this rule (nor does it serve as acompliant solution to something in this rule).

        1. The rule has nothing to do with 1-element constructors in particular though. It also happens for constructors with no elements (the second NCCE), and constructors with multiple elements, like this:

          #include <iostream>
           
          struct Widget {
            explicit Widget(int i) { std::cout << "Widget constructed" << std::endl; }
          };
           
          struct Gadget {
            explicit Gadget(Widget w1, Widget w2) { std::cout << "Gadget constructed" << std::endl; }
          };
           
          int main() {
            int i = 3, j = 4;
            Gadget g(Widget(i), Widget(j));
            std::cout << i << std::endl << j << std::endl;
          }
          1. Disagree. 1-element constructors are a major source of vexing parses. They are why WG21 added the 'explicit' keyword to sidestep vexing parses.

            1. I think we're hung up on a terminology quibble: the most vexing parse refers exclusively to something intended as an expression statement being treated as a declaration. Your example is absolutely a problem and it should definitely be mentioned in a rule; I just don't think it fits in this particular rule.

              1. I quite agree with you. (well, I will once I have an answer to my question). My code may belong in a rule, but not this one.

  2. I think our first noncompliant example is a bad one, since the most vexing parse isn't the only reason why it's wrong. Even if you fixed the ambiguity by writing "std::unique_lock<std::mutex>{m};" instead of "std::unique_lock<std::mutex>(m);", it would still be wrong, because the order of operations would be "take lock → release lock → modify shared resource", rather than the correct "take lock → modify shared resource → release lock". We fix that too by giving the variable a name, but we don't mention that's important.

    1. I suspect this code example came into being before using {} for object initialization became a thing. So the explanation is good, but isn't complete.

      I'll suggest you add a 2nd NCCE that uses {}, along with explanation. Then the CS can explain why both problems are fixed.

      1. FWIW, {} was in existence when I updated this content, but it wouldn't help (as Joseph points out). The crux of the rule isn't about the most vexing parse, however, it's about syntactically ambiguous declarations. The reason why I want to keep the first NCCE is because that's probably one of the most common syntactically ambiguous constructs I found in the wild that isn't always caught by implementations. (Implementations may have gotten better about diagnosing that code in the interim.)