C++ 2003 standard, holy ISO/EIC 14882 (among many other things) says:
6.8 Ambiguity resolution... which mentions ...There is an ambiguity in the grammar involving expression-statements and declarations: An expression-statement with a function-style explicit type conversion (5.2.3) 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.
5.2.3 Explicit type conversion (functional notation)A simple-type-specifier (7.1.5) followed by a parenthesized expression-list constructs a value of the specified type given the expression list.
Scott Meyers coined the term in his Effective STL for disambiguation rule application which parses code as function pointer declaration.
Notorious example of unexpected parsing looks like this line:
ObjectHandle rudolf( std::string() );
Unaware programmer intended to create local variable rudolf
ObjectHandle
std::string
to it's constructor
However it is understood by compiler as declaration of function rudolf
std::string(void)
ObjectHandle
However omnipresent the previous example is it's not the only tricky consequence of rule 6.8. Consider this code and try to imagine how it might work:
#include <iostream>
#include <string>
#include <cstdlib>
struct LogMe {
LogMe( ) { };
LogMe( const std::string& info ) { std::clog << info << std::endl; };
};
int main() {
const std::string object_handle /* = input */;
const std::string operation_handle /* = input */;
try {
/* do_stuff(object_handle, operation_handle); */
throw 42;
} catch ( ... ) {
LogMe(object_handle);
LogMe(operation_handle);
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
You probably nailed it straight away but I definitely did not I when I first encountered this effect of 6.8. There is a big issue with logging in catch block because neither of those two calls to LogMe::LogMe()
does anything.
Let's check more lightweight example below to understand principle of this bug. Hover over code to see comments in tooltips.
quirky constructor call
#include <iostream>
struct Object {
Object( ) {
std::cout << "default ctor" << std::endl;
}
Object( const int& ) {
std::cout << "param ctor" << std::endl;
}
};
int main() {
int a = 5;
Object(1); // Works as I expected.
// Object(a); // compilation error
{
Object(a); // Works as I expected.
}
return 0;
}
outputparam ctor
default ctor
Object(1);
is ok. There's no ambiguity because you can't declare anything named 1
. On the other hand Object(a);
looks strange and even more so because it depends on the "context".
Yes, it is rule 6.8 in action once more - it says Object(a);
is to be interpreted as declaration... But about declarations standard also has something to say:
3.1 Declarations and definitionsWhich basically means that declaration of a class instance is also definition....
A declaration is a definition unless it declares a function without specifying the function's body (8.4), it contains the extern specifier (7.1.1) or a linkage-specification (7.5) and neither an initializer nor a function-body, it declares a static data member in a class declaration (9.4), it is a class name declaration (9.1), or it is a typedef declaration (7.1.3), a using-declaration (7.3.3), or a using-directive (7.3.4).
Taken all together it means that Object(a);
a
a
a
has already been used for integer variable One Definition Rule is violated and compiler generates error. Alas, in case a
is defined only in enclosing scope, new object a
of type Object
silently shadows the int a
. Ouch...
I hope that it is now beyond any doubt that LogMe
in catch block had absolutely no chance. Shadowing std::string
objects object_handle
and operation_handle
in catch block means that default constructed Object
s are created and bye bye logging. (I, the author, thereby acknowledge that the example is artificial and defining unnecessary default constructors is evil.)
For those of us fortunate enough to be able to use 2011 or newer standard of C++ the solution is to use the list initialization syntax: Object{a};
that has no such ambiguity. Mind the fact that ambiguity resolution rules are still valid and you can enjoy all these fine effects even in code written according to new standards. Stick to the list initialization and you should be fine.
The rest of us just need to stay vigilant and pay attention to compiler warnings :-)