Code doodles #5 – quite surprising parse

I came upon a similar piece of code during an IRC discussion. While I am certain that some may consider this example trivial, I admit that the correct answer eluded me even after I verified the result with the compiler – it wasn’t until I checked the standard that it became clear.

Consider the following class:

struct foo
{
    foo()
    {
        cout << __PRETTY_FUNCTION__ << endl;
    }
 
    foo(int)
    {
        cout << __PRETTY_FUNCTION__ << endl;
    }
};

And now, consider the following code:

int global;
int main()
{
    foo();
    foo(42);
    foo(global);
}

Would you care to guess what will be printed?

Spacer to avoid \Spacer to avoid “spoilers”

Well, the answer is:

foo::foo()
foo::foo(int)
foo::foo()

[link]

The line foo(global); doesn’t cause the foo::foo(int) constructor to run, instead causing the default one to be executed. Why is that?

The answer is fairly simple, if vexing, as was spoilt in the title: the C syntax rules, that C++ was saddled with, declare that

N4140 § 6.8 [stmt.ambig] / 1
An expression-statement with a function-style explicit type conversion ([expr.type.conv]) 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.

It then follows with an example that depicts a nearly identical problem:

N4140 § 6.8 [stmt.ambig] / 2

class T {
  // ...
public:
  T();
  T(int);
  T(int, int);
};
T(a);               //  declaration

This is exactly the same principle that allows the Most Vexing Parse to exist, and that drove the standards committee to design mostly uniform initialiation added in C++11 (and made even less uniform in C++17).

In this case, it meant that foo(global); declared a new local variable called global, shadowing the global global variable. The parentheses here were entirely optional and the meaning of this declaration was identical to foo global;.

int global;
int main()
{
    foo();
    foo(42);
    foo global; // the same thing!
}

That is also the reason why the default constructor was executed.

All in all, this whole situation could be avoided with the uniform initialization:

foo{global}; // calls foo:foo(int)
foo(global); // declares global as a local variable, initializes calling
             // foo::foo()

One thought on “Code doodles #5 – quite surprising parse

Leave a Reply

Your email address will not be published.