C++ Quiz #1
2017-12-01 · C++ · Programming · WorkWhat is the output of this small snippet that is based on real C++ code?
The code compiles without warnings with clang++ 5.0.0 as well as g++ 7.2.0 using -Wall -Wextra
.
Solution
The preliminaries are simple:
- 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
andm_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 withm_i
andm_j
as parameters, soint, int
is printed. The resulting Foo object is then passed to the functionf
.f(Foo(m_i))
works analogously, it calls the Foo constructor withm_i
as parameter, soint
is printed. The resulting Foo object is then passed to the functionf
.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 withm_i
as the parameter. Instead it creates a Foo object of the namem_i
, just likeFoo m_i
would. Sostandard
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 theint m_i
member of Bar. Insteadm_i
is now referring to a local variable of typeFoo
, soFoo(m_i, m_j)
printsFoo, int
.
Explanation
Thus spoke the C++ standard:
§8.3 Meaning of declarators
[…]
6 In a declaration
T D
whereD
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);
^ ~