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!

Boann
  • 48,794
  • 16
  • 117
  • 146
Amomum
  • 6,217
  • 8
  • 34
  • 62
  • 2
    It's silly, but valid with the latest standard, yes. – user0042 Dec 29 '17 at 21:21
  • 1
    This syntax works for any type, not just string objects. [See here](https://www.ideone.com/Bh8ygA) – PaulMcKenzie Dec 29 '17 at 21:23
  • Isn't the simple answer "because the language grammar allows it"? And disallowing it might be difficult? –  Dec 29 '17 at 21:25
  • It results in the creation of the object because that's what constructors are for. Are you asking why it *compiles*? – user207421 Dec 29 '17 at 21:26
  • 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. – Amomum Dec 29 '17 at 21:29
  • 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). – Cheers and hth. - Alf Dec 29 '17 at 21:30
  • 2
    @PaulMcKenzie 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? – Amomum Dec 29 '17 at 21:32
  • 1
    @Cheersandhth.-Alf actually the most prominent occurense of this thing is `std::unique_lock(m_mutex);` So I can't not think about it. – Amomum Dec 29 '17 at 21:34
  • @Amomum _"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! – user0042 Dec 29 '17 at 21:41
  • @user0042 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? – Amomum Dec 29 '17 at 21:41
  • Depends if the name has a previous definition. – Cheers and hth. - Alf Dec 29 '17 at 21:43
  • @Amomum _"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. – user0042 Dec 29 '17 at 21:43
  • @user0042 that's a fair point but my point was that grammatically these things are identical. – Amomum Dec 29 '17 at 21:44
  • @Amomum _"but my point was that grammatically these things are identical."_ They aren't. That totally depends on context as mentioned. – user0042 Dec 29 '17 at 21:46
  • @user0042 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? – Amomum Dec 29 '17 at 21:48
  • @Amomum `std::unique_lock(m_mutex)` actually will work fine - `std::unique_lock(m_mutex)` is the situation you're talking about. – Barry Dec 29 '17 at 21:49
  • @Barry isn't the first example the same as the second, apart from deduced type? – Amomum Dec 29 '17 at 21:53
  • 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. – Justin Dec 29 '17 at 21:55
  • 2
    @Amomum `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) – Barry Dec 29 '17 at 21:55

1 Answers1

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • 1+. Is the first considered an `r-value`? – Raindrop7 Dec 29 '17 at 21:41
  • @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. – Harrand Dec 29 '17 at 21:43
  • 1
    Uhm, I removed my downvote after considering that you're probably on to the right place in the standard, just that the standard's wording (talking about function declarations) is misleading. – Cheers and hth. - Alf Dec 29 '17 at 21:46
  • Following the same logic `int(a) = 1; std::cout << a;` is perfectly fine – Killzone Kid Dec 29 '17 at 21:49
  • 1
    @KillzoneKid Yep, that declares an `int` named `a`, initialized with `1` and then prints it. – Barry Dec 29 '17 at 21:53
  • @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". – David A Dec 29 '17 at 22:47