5

As int() and int{} are constant expressions of value equal to 0, I thought they are equivalent and interchangeable, thus compilers must treat them equally. For example,

 int a[0];      //error: zero-sized array not allowed in ISO C++
 int b[int()];  //error: zero-sized array not allowed in ISO C++
 int c[int{}];  //error: zero-sized array not allowed in ISO C++

But it seems there are some corner cases where they're not interchangeable.

  • When initializing a pointer:

    int *p = 0;     //ok
    int *q = int(); //error - by clang only
    int *r = int{}; //error - by gcc and clang both
    

    See GCC and Clang messages. I suspect this is a bug in both compilers, as I expect them to be interchangeable in this context, but I would be glad to be proven wrong. :-)

  • When passing to class template:

    template<int N> struct X{};
    
    X<0>      x1; //ok
    X<int{}>  x2; //ok (same as X<0>)
    X<int()>  x3; //error  
    

    See GCC and Clang messages.

    I find the syntax X<int()> quite familiar as I've seen (and probably used) the similar syntax before, such as in std::function<int()>, the template argument int() is expected to be function type (instead of 0) taking no argument and returning int. But I want to know the section of the spec which says in this context int() is to be treated as function type and is not equivalent to int{} which is always 0.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • 6
    So this is basically a "grep the standard for me" question, right? – Griwes Oct 01 '13 at 17:11
  • Do you expect the relevant standard text to be in a different place from the text that defines the "most vexing parse"? – Steve Jessop Oct 01 '13 at 17:27
  • 1
    I like it too. It's a very good question---my impression is that the standard isn't clear about it (but I'm still studying it). – James Kanze Oct 01 '13 at 17:27
  • 1
    @SteveJessop Do you think the most vexing parse applies to any of the cases here? I don't see it, except maybe for `X`. And even then, I think the compiler first finds `X`, and so knows that it's not looking for a type. – James Kanze Oct 01 '13 at 17:30
  • @SteveJessop: *I think* I know the context of most vexing parse : when a syntax can be interpreted as two ways : function declaration and (named) object declation, then function declaration is preferred. But I don't think that applies here. I don't see named object here. So I am unable to relate. :( – Nawaz Oct 01 '13 at 17:30
  • 1
    Regarding `X`: **14.3 [temp.arg]** "In a *template-argument*, an ambiguity between a *type-id* and an *expression* is resolved to a *type-id*, regardless of the form of the corresponding *template-parameter*." – willj Oct 01 '13 at 17:30
  • @Nawaz You don't need named objects. But you do need a context where a declaration would be legal. I think this excluded the "initialization of a pointer". – James Kanze Oct 01 '13 at 17:31
  • @JamesKanze: it was `X` I was thinking of, I couldn't remember the wording of the vexation so wasn't sure whether or not it would imply parsing as a function type here. Apologies that my remark wasn't helpful. – Steve Jessop Oct 01 '13 at 17:32
  • @JamesKanze: Ohh, then I've to see it again, because so far I've seen most vexing parse comes into picture when *name* is involved, be it name of a function or object. – Nawaz Oct 01 '13 at 17:32
  • 1
    @SteveJessop Yes. That one seems questionable to me, too. C++ generally tries to avoid requiring semantic information to drive the parsing. (It doesn't always succeed.) In the case of `X`, the _grammatical context_ allows a type declaration, so unless the standard requires the compiler to take semantic information (the fact that the template parameter isn't a type parameter) into account, it's a type declaration (and another instance of the most vexing parse. – James Kanze Oct 01 '13 at 17:35
  • 1
    The real question, of course, is whether `int()` or `int{}` are constant expressions. The text in §15.9 doesn't line up easily with the rest of the standard: it speaks in part about "invoking a `constexpr` constructor, but there is no expression which "invokes a constructor" (and `int` doesn't have a constructor). Formally, `int()` and `int{}` are "Explicit type conversions". – James Kanze Oct 01 '13 at 17:43
  • A value initialized `int` and the literal `0` are different things – David Rodríguez - dribeas Oct 01 '13 at 18:01
  • @DavidRodríguez-dribeas: They're constant expressions, aren't they? If so, in what ways they're different as per the spec? – Nawaz Oct 01 '13 at 18:03

1 Answers1

5

The expressions int() and int{} are both constant expression prvalues of integer type that evaluate to zero, and are therefore interchangeable with the literal 0 in any context that requires an integral constant expression prvalue of integer type which evaluates to zero.

Both expressions satisfy the requirements for a constant expression as specified in 5.19 Constant Expressions [expr.const].

Regarding X<int()>, the standard specifies that int() is not interpreted as an expression in this context:

14.3 Template arguments [temp.arg]

In a template-argument, an ambiguity between a type-id and an expression is resolved to a type-id, regardless of the form of the corresponding template-parameter.

Regarding pointer conversions:

4.10 Pointer conversions [conv.ptr]

A null pointer constant is an integral constant expression (5.19) prvalue of integer type that evaluates to zero or a prvalue of type std::nullptr_t.

Based on the above paragraph, both int() and int{} are null pointer constant expressions. This points to a (very minor) compiler bug, although there is an open Defect Report (903) which may lead to this paragraph changing:

There was a strong consensus among the CWG that only the literal 0 should be considered a null pointer constant, not any arbitrary zero-valued constant expression as is currently specified.

The following wording deals with the value of the expression int():

8.5 Initializers [dcl.init]

To zero-initialize an object or reference of type T means:

[omitted clauses which don't apply]

if T is a scalar type (3.9), the object is set to the value 0 (zero), taken as an integral constant expression, converted to T

[...]

To value-initialize an object of type T means:

— if T is a (possibly cv-qualified) class type (Clause 9) with a user-provided constructor (12.1), then the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);

[omitted clauses which don't apply]

otherwise, the object is zero-initialized.

An object whose initializer is an empty set of parentheses, i.e., (), shall be value-initialized.

And for the value of int{}:

8.5.4 List-initialization [dcl.init.list]

List-initialization of an object or reference of type T is defined as follows:

— If the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.

[omitted clauses which don't apply]

Otherwise, if the initializer list has no elements, the object is value-initialized.

All quotes from C++ Working Draft Standard N3337.

willj
  • 2,991
  • 12
  • 24
  • For the definition of *prvalue*, see **3.10 [basic.lval]**. This definition includes literal `0`. – willj Oct 01 '13 at 18:21
  • Change of CWG opinion then, because the question which integral expressions are valid null pointer constants has come up before. And back then we explicitly allowed `false` as a NPC. Not `1-1`, though, as this predates `constexpr`, and we therefore had no way to express what was "const enough". – MSalters Oct 01 '13 at 22:42
  • @MSalters There's an additional note in DR 903: *Concerns were raised at the Portland (October, 2012) meeting that the value false has been used in existing code as a null pointer constant, and such code would be broken by this change. This issue has been returned to "review" status to allow discussion of whether to accommodate such code or not.* – willj Oct 02 '13 at 09:41