Why does using parentheses with a default constructor result in creation of the variable? [duplicate]
Asked Answered
D

1

6

After watching Louis Brandy talk at CppCon 2017 I was shocked to discover that this code actually compiles:

#include <string>

int main() {

    std::string(foo);

    return 0;
}    

And for some reason std::string(foo) it is identical to std::string foo i.e. declaring a variable. I find it absolutely counterintuitive and can't see any reason for C++ to work that way. I would expect this to give an error about undefined identifier foo.

It actually makes expressions like token1(token2) have even more possible interpretations than I previously thought.

So my question is: what is the reason for this horror? When is this rule actually necessary?

P.S. Sorry for the poorly worded title, please, feel free to change it!

Dierdre answered 29/12, 2017 at 21:18 Comment(19)
It's silly, but valid with the latest standard, yes.Violist
This syntax works for any type, not just string objects. See hereAvrilavrit
Isn't the simple answer "because the language grammar allows it"? And disallowing it might be difficult?Ceja
It results in the creation of the object because that's what constructors are for. Are you asking why it compiles?Kelsiekelso
I totally expected it not to compile and say something like "undefined identifier 'foo'". I don't understand why parens are just ommited in this case.Dierdre
It's just a silly syntax inherited from C. Think no more about it. Ask rather why you can overload a function and struct name (same reason, but more likely to trip you up).Peeress
@Avrilavrit so if x is already defined, int(x) is type cast but if it's not defined, int(x) is a variable declaration. Seriosly, what the hell?Dierdre
@Cheersandhth.-Alf actually the most prominent occurense of this thing is std::unique_lock(m_mutex); So I can't not think about it.Dierdre
@Dierdre "the most prominent occurense ...std::unique_lock(m_mutex);" That's something completely different. Do you know what you're actually talkng about? "Seriously, what the hell" is context dependent and you should know how to eplore the context dude!Violist
@Violist i believe this to be the same. This declares a local default-constructed unique_lock named 'm_mutex'. It doesn't lock anything. It works because unique_lock has ctor with no arguments. Am I wrong?Dierdre
Depends if the name has a previous definition.Peeress
@Dierdre "It works because unique_lock has ctor with no arguments." Though that's not a language deficiency, but a debatable question why std::unique_lock provides a default constructor at all.Violist
@Violist that's a fair point but my point was that grammatically these things are identical.Dierdre
@Dierdre "but my point was that grammatically these things are identical." They aren't. That totally depends on context as mentioned.Violist
@Violist I can't understand in what context this won't the same. It doesn't matter if m_mutex already exists; if it does, it will be shadowed, if it doesn't, it will be declared. What am I missing?Dierdre
@Dierdre std::unique_lock(m_mutex) actually will work fine - std::unique_lock<std::mutex>(m_mutex) is the situation you're talking about.Derina
@Derina isn't the first example the same as the second, apart from deduced type?Dierdre
It seems to me that parentheses are allowed in declarations because you need at least some parentheses to be able to declare some types involving function pointers or pointers and arrays.Illustration
@Dierdre std::unique_lock(m_mutex); would be std::unique_lock m_mutex, which is ill-formed because class template argument deduction fails. So, you'll get a compiler error (which is what I meant by "fine" - as in, it wont' do the wrong thing)Derina
D
13

Since this question is tagged , the direct answer is that, from [stmt.ambig]:

There is an ambiguity in the grammar involving expression-statements and declarations: An expression-statement with a function-style explicit type conversion 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.

And, similarly, for functions, in [dcl.ambig.res]:

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 a declaration.

Hence:

Why oh why is std::string("foo") so different from std::string(foo)

The former cannot be a declaration. The latter can be a declaration, with a redundant set of parentheses. Thus, the former isn't a declaration and the latter is.

The underlying issue is that, grammaticaly, declarators can start with a ( which could make it indistinguishable from a function-style explicit type conversion. Rather than come up with arbitrary complex rules to try to determine what the user meant, the language just picks one, and it's easy enough for the user to fix the code to actually do what he meant.

Derina answered 29/12, 2017 at 21:37 Comment(6)
1+. Is the first considered an r-value?Radicalism
@KillzoneKid I'd say it's more along the lines of following the specification (to consider any construct that could possibly be a declaration a declaration) more strongly than normal human expectation, as opposed to the compiler 'thinking' the programmer is an idiot.Ladybug
@Barry: :-) ....'Peeress
Following the same logic int(a) = 1; std::cout << a; is perfectly fineCacodemon
@KillzoneKid Yep, that declares an int named a, initialized with 1 and then prints it.Derina
@Barry: not only that, but the declaration works in vanilla C (even with all the warnings turned on (and with even more parentheses)). I would imagine C++ inherited this "feature".Zoa

© 2022 - 2024 — McMap. All rights reserved.