What Is the Purpose of the Most Vexing Parse

What is the purpose of the Most Vexing Parse?

Let's say MVP didn't exist.

How would you declare a function?

A foo();

would be a variable definition, not a method declaration. Would you introduce a new keyword? Would you have a more awkward syntax for a function declaration? Or would you rather have

A foo;

define a variable and

A foo();

declare a function?

Your slightly more complicated example is just for consistency with this basic one. It's easier to say "everything that can be interpreted as a declaration, will be interpreted as a declaration" rather than "everything that can be interpreted as a declaration, will be interpreted as a declaration, unless it's a single variable definition, in which case it's a variable definition".

This probably isn't the motivation behind it though, but a reason it's a good thing.

Most vexing parse C++11

what exactly this line does

It creates a temporary X, value-initialising it by calling the default constructor, and then uses that to initialise a Y variable, calling the const X& conversion constructor.

where is connection to Most vexing parse

If you were to try to write this using old-school initialisation syntax

Y y (X());

then the so-called "most vexing parse" would interpret this as a function, rather than a variable, declaration: a function called y, with return type Y and a single parameter, whose type is a (pointer to a) function returning X.

You could add extra parentheses, so that it can't be interpreted as a function declaration:

Y y ((X()));

or, since C++11, you can use brace-initialisation as your example does.

Most vexing parse

what does this syntax int(x) in the statement Foo f( int(x) ); mean?

The parentheses around x are superfluous and will be ignored. So int(x) is the same as int x here, which means a parameter named x with type int.

Is it the same as Foo f( int x );?

Yes. Foo f( int(x) );, is a function declaration which is named f, returns Foo, takes one parameter named x with type int.

Here's the explanation from the standard. [dcl.ambig.res]/1:

(emphasis mine)

The ambiguity arising from the similarity between a function-style
cast and a declaration mentioned in [stmt.ambig] can also occur in the
context of a declaration. In that context, the choice is between a
function declaration with a redundant set of parentheses around a
parameter name and an object declaration with a function-style cast as
the initializer. Just as for the ambiguities mentioned in
[stmt.ambig], the resolution is to consider any construct that could
possibly be a declaration
.

Note: A declaration can be
explicitly disambiguated by adding parentheses around the argument.
The ambiguity can be avoided by use of copy-initialization or
list-initialization syntax, or by use of a non-function-style cast.

struct S {
S(int);
};

void foo(double a) {
S w(int(a)); // function declaration
S x(int()); // function declaration
S y((int(a))); // object declaration
S y((int)a); // object declaration
S z = int(a); // object declaration
}

So, int(x) will be considered as a declaration (of the parameter) rather than a function style cast.

Understanding 'most vexing parse' - why allow ambiguous syntax?

So why not simply disallow TimeKeeper time_keeper(Timer()) to be a function declaration that takes an unnamed function ptr returning type Timer?

Suppose for a while that this function declaration is diallowed, because it uses unnamed parameter. If that is so, then the following declarations will be disallowed as well:

int max(int,int);  //error (in hypothetical C++)
int min(int,int); //error (in hypothetical C++)

And then the programmers will be forced to write the parameter name, in the declarations as well:

int max(int a,int b);  //ok 
int min(int a,int b); //ok

But then someone else would stand up and ask : "Why am I forced to write the parameter name(s) in the declarations when it doesn't use it? Why is it not optional?"

I think this guy is rational and what he asked has point. It is indeed irrational to force programmers to name the parameter in the declarations.

--

Reading your comment, it seems that you think the following declarations are exactly same:

int max(Timer());
int max(Timer(*)());

No. They're not exactly same from the syntax point of view, though they are exactly same from the behavior point of view.

The subtle difference is that in the former, the parameter type is a function which takes nothing, and returns Timer, while in the later, the parameter type is a pointer to a function which takes nothing, and returns Timer. Do you see the difference?

But then the question is, why are they same behavior-wise? Well the answer is, in the former declaration, the parameter type is adjusted and then becomes a pointer type, and so it behaves the same as the second declaration.

The C++03 Standard says in §13.1/3,

Parameter declarations that differ only in that one is a function type
and the other is a pointer to the same function type are equivalent.
That is, the function type is adjusted to become a pointer to function
type
(8.3.5).

I hope it is same in C++11 also.

--

Your doubt (taken from the comment):

Still not any closer to understanding why we need the 2 syntax?

Because they are two different types. Function type, and pointer to function type. Only as parameter type, they behaves same. Otherwise, they're different. See my answer here to see where they behave differently:

  • Reference to Function syntax - with and without &

And since they behave differently in other situations, we have them, we need them. The Standard doesn't (and should not) disallow one syntax, just because as parameter type they behave same.

(Why) Is this an example of a vexing parse?

The "canonical" meaning of the "most vexing parse" refers to the ambiguity between object declaration and function declaration.

What you have in your case is a different ambiguity: an ambiguity between object declaration and functional-style cast (more formally: functional notation of explicit type conversion, see 5.2.3). This latter ambiguity is resolved in favor of object declaration. Hence the error. Your code is seen by the compiler as a simple

ScopeLock m_;

which makes it to complain about missing default constructor.

6.8 Ambiguity resolution [stmt.ambig]

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

Whether you want to call it another flavor of "most vexing parse" is up to you.

There are many different ways to make the compiler to interpret it as an expression instead of declaration. You can also do it as

0, ScopeLock(m_);

or as

(ScopeLock(m_));

Most vexing parse

This is due to the fact that TimeKeeper time_keeper(Timer()); is interpreted as a function declaration and not as a variable definition. This, by itself, is not an error, but when you try to access the get_time() member of time_keeper (which is a function, not a TimeKeeper instance), your compiler fails.

This is how your compiler view the code:

int main() {
// time_keeper gets interpreted as a function declaration with a function argument.
// This is definitely *not* what we expect, but from the compiler POV it's okay.
TimeKeeper time_keeper(Timer (*unnamed_fn_arg)());

// Compiler complains: time_keeper is function, how on earth do you expect me to call
// one of its members? It doesn't have member functions!
return time_keeper.get_time();
}

A confusing detail about the Most Vexing Parse

istream_iterator<int>(cin) is exactly the same as istream_iterator<int> cin but with superfluous parens. This declarator syntax was inherited from C, and I think even the inventor of C (Ken Thompson?) described it as a mistake.

Most vexing parse even more vexing

Hold on to your chair since it's pretty funny. As you surely know C++ allows array function parameters. And so you can get this:

void foo(double s[2], double b[2]);

This is obvious. A possible obfuscation step is to replace spaces between type and parameters name which is also allowed:

void foo(double(s[2]),double(b[2]));

Now you can imagine what can be done pretty simply - replace numbers with const char*. Like this:

void foo(double(s["x"]),double(b["y"]));

This is invalid function declaration, nevertheless it is seen by the compilers as exactly this - declaration. This is exactly what happened to your code.

EDIT:
The whole problem seems to arise from not strict enough restrictions on array declarators in C++ standard. The only requirement for array 'size' parameter is being constexpr value which is supposed to be converted to std::size_t (but it is not checked on the level of syntax analysis, it is done later on). For more on that check this

How can I avoid most vexing parse with direct value initialization?

Use copy initialisation and rely on C++17's guarantee that copy elision will happen.

For example:

struct Foo
{
Foo() = default;
Foo(Foo const&) = delete;
};

int main()
{
auto f = Foo();
}

https://godbolt.org/g/9tbkjZ



Related Topics



Leave a reply



Submit