Weird diagnostic about non-type template argument

The following typescript show the issue:

$ cat t.cc

template <int *ptr>
struct S1 { };

template <bool b>
struct S2 { };

int v1;

S1<(&v1)> x;
S2<(2>2)> y;
$ clang -c t.cc
t.cc:10:4: error: non-type template argument cannot be surrounded by
parentheses
S1<(&v1)> x;
   ^~~~~
1 error generated.

My notes:

1) in my opinion the diagnostic is due to a needless too strong
interpretation of the standard:

- comeau and intel does not mark this as an error

- gcc seems to mark this as an error due to some bug (the diagnostic is
very weird)

2) also if there is disagreement about 1 I don't see the reason to
generate an error instead of a warning

3) that apart the diagnostic message is misleading: non-type template
argument might (and sometimes should, see the following line in source)
be surrounded by parentheses

The following typescript show the issue:

$ cat t.cc

template <int *ptr>
struct S1 { };

template
struct S2 { };

int v1;

S1<(&v1)> x;
S2<(2>2)> y;
$ clang -c t.cc
t.cc:10:4: error: non-type template argument cannot be surrounded by
parentheses
S1<(&v1)> x;
^~~~~
1 error generated.

My notes:

  1. in my opinion the diagnostic is due to a needless too strong
    interpretation of the standard:
  • comeau and intel does not mark this as an error

  • gcc seems to mark this as an error due to some bug (the diagnostic is
    very weird)

The standard seems really really clear here – the argument is not an expression, it’s an address. You’re only provided one mechanism for writing an address in this context: ‘& id-expression’, with the ‘&’ being optional in certain cases. You can’t even write ‘0’ here. I think it’s actually useful to preclude parentheses as they make this look like an expression, which it simply is not. You can’t write ‘(&foo + sizeof(int))’ to advance past one element of an int array either.

  1. also if there is disagreement about 1 I don’t see the reason to
    generate an error instead of a warning

A ExtWarn seems not implausible here, but I’m not sure why supporting this is important.

  1. that apart the diagnostic message is misleading: non-type template
    argument might (and sometimes should, see the following line in source)
    be surrounded by parentheses

The second line is a completely different non-type template argument: an integral constant-expression. There are no parentheses around the template argument, there are parentheses as part of the constant-expression, which allows parentheses among many other constructs. It is certainly unfortunate that there is no way to write a ‘>’ in an ICE without parentheses, but I don’t think that artifact necessarily warrants allowing parentheses for all template arguments, or even for all non-type template arguments…

Hmmm... perhaps not so clear...

If you look at standard grammar, you read this:

template-argument:
  constant-expression
  type-id
  id-expression

Of course S1<v1> is in the 'id-expression' case, but both S1<&v1> and
S1<(&v1)> are in 'constant-expression' case.

Don't you think that if the intention was to inhibit parenthesis use the
standard would have been written as:

template-argument:
  constant-expression
  type-id
  '&'opt id-expression

?

No, because the general grammar is not meant to be normative. You can’t read one piece of grammar and know what is or is not a valid program. In this case, you’re citing text from 14.2 (C++0x rather than '03, but I’ll stick with '0x for now – if anything '03 is more restrictive here!), but the detail of what goes into a template argument is more fully explained by 14.3.2 [template.arg.nontype]. Here, it clearly lists what constant-expression arguments are allowed:
— an integral constant expression (including a constant expression of literal class type that can be used as an integral constant expression as described in 5.19); or
— a constant expression that evaluates to a null pointer value (4.10); or
— a constant expression that evaluates to a null member pointer value (4.11); or

That’s it. The only way you get an address of an actual object is:
— the address of an object or function with external linkage, including function templates and function template-ids but excluding non-static class members, expressed as & id-expression where the & is optional if the name refers to a function or array, or if the corresponding template-parameter is a reference

This is very clear that it is restricting the syntax allowed to represent the address of an object. It seems to specifically preclude pointer arithmetic and other expressions on the address.

The following typescript show the issue:

$ cat t.cc

template <int *ptr>
struct S1 { };

template
struct S2 { };

int v1;

S1<(&v1)> x;
S2<(2>2)> y;
$ clang -c t.cc
t.cc:10:4: error: non-type template argument cannot be surrounded by
parentheses
S1<(&v1)> x;
^~~~~
1 error generated.

My notes:

  1. in my opinion the diagnostic is due to a needless too strong
    interpretation of the standard:
  • comeau and intel does not mark this as an error

  • gcc seems to mark this as an error due to some bug (the diagnostic is
    very weird)

The standard seems really really clear here – the argument is not an
expression, it’s an address. You’re only provided one mechanism for
writing an address in this context: ‘& id-expression’, with the ‘&’
being optional in certain cases. You can’t even write ‘0’ here. I think
it’s actually useful to preclude parentheses as they make this look like
an expression, which it simply is not. You can’t write ‘(&foo +
sizeof(int))’ to advance past one element of an int array either.

Hmmm… perhaps not so clear…

If you look at standard grammar, you read this:

template-argument:
constant-expression
type-id
id-expression

Of course S1 is in the ‘id-expression’ case, but both S1<&v1> and
S1<(&v1)> are in ‘constant-expression’ case.

Don’t you think that if the intention was to inhibit parenthesis use the
standard would have been written as:

template-argument:
constant-expression
type-id
'&'opt id-expression

?

No, because the general grammar is not meant to be normative. You can’t read one piece of grammar and know what is or is not a valid program. In this case, you’re citing text from 14.2 (C++0x rather than '03, but I’ll stick with '0x for now – if anything '03 is more restrictive here!), but the detail of what goes into a template argument is more fully explained by 14.3.2 [template.arg.nontype]. Here, it clearly lists what constant-expression arguments are allowed:
— an integral constant expression (including a constant expression of literal class type that can be used as an integral constant expression as described in 5.19); or
— a constant expression that evaluates to a null pointer value (4.10); or
— a constant expression that evaluates to a null member pointer value (4.11); or

For reference, these two are what are missing in C++03. In '03 you can’t even write ‘0’ as a template argument for a pointer non-type template argument. In '0x, you can, you can even write a complex const-expr to do so. But you can’t make it evaluate to any other pointer value, for that you have to use the rule below.

Chandler Carruth wrote:

[... snip ...]

    3) that apart the diagnostic message is misleading: non-type template
    argument might (and sometimes should, see the following line in source)
    be surrounded by parentheses

The second line is a completely different non-type template argument: an integral constant-expression. There are no parentheses around the template argument, there are parentheses as part of the constant-expression, which allows parentheses among many other constructs. It is certainly unfortunate that there is no way to write a '>' in an ICE without parentheses, but I don't think that artifact necessarily warrants allowing parentheses for all template arguments, or even for all non-type template arguments...

I think that, in point 3, Abramo was just referring to the wording of the diagnostic, which maybe could be improved.

I don't think that usual C++ programmers can easily tell the difference (if any) from a non-type template argument and the constant expression that denotes a non-type template argument. So, it might be appropriate to change the message wording to something like:

non-type template argument having pointer type cannot be surrounded by paretheses
    or
pointer-type expression used as non-type template argument cannot be ...

or something similar.

Cheers,
Enea.

Sure. I’ve no problem improving the diagnostic, but I’m not sure what to make it. Your first is close though, maybe: “a non-type template argument which is the address of an object cannot be surrounded by parentheses”. I’m leery of using words like “type” because of the “non-type” category of template arguments we’re dealing with. I’m not the best wordsmith though…

Furtherly to exit with a fatal error is a bit overkill for things like
that... do you agree?

As I said in my original post, this being an ExtWarn seems plausible, but I don’t personally find it compelling to support this syntax. I think it’s actually worthwhile that the standard insists on a differentiated syntax in these two cases for the reason I originally pointed out – this isn’t an expression, and it shouldn’t be thought of in that context.

Perhaps others disagree, or have some large body of code that uses this extensively (seems rather unlikely to me though…), but either way, the code doesn’t conform, and so it should at most be accepted as an extension with appropriate warnings.

This is core issue 773:

http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#773

And the committee is favoring allowing the parentheses. I suggest we make it an ExtWarn in C++98/03 mode, with clearer diagnostic text (if we can).

  • Doug