/** * @file

Vexing parses

*/
#define C++03
#define SURPRISING_CODE_PARSING

Ambiguity resolution

C++ 2003 standard, holy ISO/EIC 14882 (among many other things) says:

6.8 Ambiguity resolution

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.

... which mentions ...
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.

The most vexing parse (?)

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

However it is understood by compiler as declaration of function rudolf

Vexing "anonymous temporary instance"

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 definitions

...
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).

Which basically means that declaration of a class instance is also definition.

Taken all together it means that Object(a);

  1. is because of 6.8 to be parsed as declaration of a
  2. so by 3.1 it means definition of a
  3. and because there is no initialization value passed a default constructor is used.
  4. In case name 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 Objects are created and bye bye logging. (I, the author, thereby acknowledge that the example is artificial and defining unnecessary default constructors is evil.)

Magical cure

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 :-)