We have a struct Foo with a few different constructors, each of which prints a text that represents its parameter types.
We have a struct Bar with two members, m_i and m_j.
The main function calls the standard constructor of Bar.
The constructor of Bar is where the magic happens, so let’s go through it line by line:
f(Foo(m_i, m_j)) creates a temporary Foo object by calling the Foo constructor with m_i and m_j as parameters, so int, int is printed. The resulting Foo object is then passed to the function f.
f(Foo(m_i)) works analogously, it calls the Foo constructor with m_i as parameter, so int is printed. The resulting Foo object is then passed to the function f.
Foo(m_i, m_j) works the same as the first line, except it doesn’t pass the resulting temporary Foo object to a function, so it is destroyed again immediately, int, int is printed again.
So far so good, but now look closely. Foo(m_i) surprisingly behaves entirely differently from all the previous lines. It does not call the Foo constructor with m_i as the parameter. Instead it creates a Foo object of the name m_i, just like Foo m_i would. So standard is printed.
Now the last line looks just like the third line, but it still does something different. Why? Because the name m_i is no longer referring to the int m_i member of Bar. Instead m_i is now referring to a local variable of type Foo, so Foo(m_i, m_j) prints Foo, int.
Explanation
Thus spoke the C++ standard:
§8.3 Meaning of declarators
[…]
6 In a declaration T D where D has the form
( D1 )
the type of the contained declarator-id is the same as that of the contained declarator-id in the declaration
T D1
Parentheses do not alter the type of the embedded declarator-id, but they can alter the binding of complex declarators.
This kind of ambiguous parsing can lead to dangerous situations, as is documented in DCL53-CPP:
The code looks like it locks the mutex m while modifiying shared_resource, but instead a new mutex m is created, shadowing the global mutex m.
Future Directions
The upcoming Clang version will have a warning for this:
y.cpp:25:12: warning: parentheses were disambiguated as redundant parentheses around declaration of variable named
'm_i' [-Wvexing-parse]
Foo(m_i);
^~~~~
y.cpp:25:9: note: add enclosing parentheses to perform a function-style cast
Foo(m_i);
^
( )
y.cpp:25:12: note: remove parentheses to silence this warning
Foo(m_i);
^ ~