The Most Vexing Parse rule

| ⌛ 3 minutes read

📋 Tags:


You have learnt in lecture that there are many ways to create threads, and certain ways of creating threads actually don’t work.

Due to time constraints + how confusing this may be, this isn’t covered in the tutorial.

A brief recap of what you see in lecture:

 0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
#include <thread>

void task();        // assume this is implemented
class TaskClass {  // callable type
    public:
    void operator()() const {
        task();
    }
};

int main() {
    std::thread t0(task);           // my favorite style. very RTOS-like.

    TaskClass my_task;
    std::thread t1(my_task);        // callable object
    std::thread t2{my_task};        // recommended approach
    std::thread t3((TaskClass()));  // () hell
    std::thread t4{TaskClass()};    // recommended approach  
    std::thread t5([](){            // lambdas are also recommended
        task();
    });

    t0.join();
    t1.join();
    t2.join();
    t3.join();
    t4.join();
    t5.join();
}

// WRONG: This returns a func that returns a std::thread object. wtf?
std::thread t6(TaskClass());

The question is: why is this so cursed?

The Most Vexing Parse

C++ is a messy language that wants to modernize with fancy modern syntactic features but MUST keep things backwards compatible1.

This results in some syntactical ambiguity on whether something is a parameter or a function type specification.

The most vexing parse is a rule applied to handle such ambiguity.

Consider this:

0
1
// Simple, right?
char foo(Bar());

What do you think line 2 means?

Generally, two schools of thought.

  1. foo is a char variable.
    • It was made with argument ‘Bar’, where ‘Bar’ has been constructed2.
  2. foo is a function declaration3 that returns char.
    • It has an unnamed argument. The argument type is a pointer to a function4.

The standard chooses 2 as the interpretation. I can’t find the exact reason why in cppreference. Maybe you’d have better luck digging through the ISO standard. This is unfortunately left as an exercise for the reader… 😰

To resolve this ambiguity, we can either wrap the parameter in () (see t3), or use uniform initialization (see t2 and t4), i.e. {} which was introduced in c++11.

You will get compile warnings if the vexing parse rule was applied to your code.

I don’t know the standard at the back of my hand, so here are some resources (and references to this writeup) to get you started on a deep-dive:

  • This mini writeup is mostly inspired by this wiki article that does a detailed job explaining the vexing parse rule. Read it to see more examples, gory details and types of vexing parses that happen, such as with C-style casting. Also, they talk about the common ambiguity resolution techniques.

  • Why most vexing parse?

TLDR: Use {} or lambdas for thread creation in assignments.


  1. C++ must be backwards compatible with C code, or else the world will explode. ↩︎

  2. More precisely, an anonymous instance of Bar (don’t worry, you don’t really need to know this). ↩︎

  3. This might be confusing. How is it a function declaration without a {} body? This is probably because prototype functions are a thing. ↩︎

  4. The wiki on vexing parse claims this is due to C++ type decay rules. ↩︎