atomgalaxy / isocpp-universal-template-param

We propose a way to spell a universal template parameter kind. This would allow for a generic apply and other higher-order template metafunctions, and certain typetraits.
https://atomgalaxy.github.io/isocpp-universal-template-param/d1985r0.pdf
2 stars 2 forks source link

Expressions involving universal templates #19

Closed camaclean closed 1 year ago

camaclean commented 1 year ago

While thinking about dependent name parsing, I think that we need to prohibit universal templates as part of larger expressions:

template<typename T>
void foo() {
  mytype2<template auto mytype::name*2> a;
}

We don't know how to parse an expression until mytype::name is disambiguated. I don't think this can be delayed until instantiation.

However, with this restriction in place, I don't see a problem with defining universal aliases. Thoughts?

BengtGustafsson commented 1 year ago

Here is your example, without the ambiguation, and with mytype replaced by T as I assume you ment:

template void foo() { mytype2<T::name*2> a; }

According to current rules T::name is a value and thus the * is a multiplication operator. If T::name turns out to be a type or template during instantiation that's an error. However, with some tweaking of the rules the case that T::name is in isolation in the template-argument it could still be parsed as a value, but during instantiation it could be any kind anyway.

template void foo() { mytype2 a; // Parsed as value, during instantation could be any kind provided mytype2 accepts it. }

This reduces the need for typename in current code somewhat also.


Here is the mail I sent, to basically the same effect, for reference:

After some further thought I think we may mostly worried about a non-problem:

The places in the syntax you mention, i.e. in template argument list and template parameter default expressions use the grammar production template-argument. This production has three alternatives:

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

These represent the three different parameter type kinds. Now, if the text being parsed looks like a dependent name, such as T::name the compiler will during parsing interpret this as a constant-expression. However, in the instantiation step the compiler must check if the dependent name is really a value, and the only thing we need from compilers is that if it is not it should not err out immediately but check if this matches the use of the template-argument. As the compiler has to produce an error today it must already have the logic to "resolve" the dependent name and the only thing that changes is the consequences of this resolving to something else than a value. If the constant-expression is more complicated than just the dependent name the parser would already have gone on to parse this in the first phase, preventing the "resolve" to allow any other result than value.

template void f() { std::vector x; // Now ok: At instantiation the compiler first checks the kind of T::nested and sees that it is ok as template-argument here. std::vector<T::nested> x2; // Not ok, the initial parsing assumes T::nested is a value and finds the trailing utterly confusing. std::vector<typename T::nested*> x3; // Ok as disambiguation was in effect forcing the parse to continue using type-id }

Yes, this requires some minor surgery to the compiler, but its not going to be complicated, and we also take down with typename a step further as a side effect.

Today both clang and gcc complain about the x declaration. I think this is not really necessary but a courtesy of these compilers. That is to say: The compiler is not oblighed to check if the template argument kind matches the template parameter kind already during parsing. Well, compilers would not have to change this behaviour either, we only care about the new case that this check reveals that the template parameter is universal in which case the presumed value is a fit and not an error. Then in instantiation if the dependent name turns out to resolve to a type or template that is ok too. This would prevent the down with typename aspect though.

This could however become problematic if we get concepts that for instance allow type and template but not value as kinds of the template parameter, so I think we should mandate that compilers don't check for matching template parameter kinds in the parsing phase if the template-argument is a dependent name that could resolve to any kind. This allows concepts for "limited" UTPs and removes the need for typename in a fair amount of use cases (for instance x declaration above).

This reasoning must definitely be in the proposal.

As the rhs of a Universal Alias would be a template-argument the same rule set would be applicable there, allowing a dependent name or an UTP as the rhs without "ambiguation".

camaclean commented 1 year ago

That's not quite what I was trying to get at. I should have just written

void foo() {
  mytype2<template auto mytype::name*2> a;
}

I was wanting to describe a UTP used as part of a larger constant expression. Obviously this can only be a value in this circumstance, but there could potentially be cases where both a value and a typename could make sense but the parsing would have a different meaning. There's a difference between parsing an expression one way and then later saying "oh, that doesn't make sense" and not being able to parse it until the type/value differentiation has been made. mytype2<template auto mytype::name> a; makes sense. mytype2<template auto mytype::name*2> a; doesn't.

I think we need to specify that if you use a UTP as a UTP, it needs to parsed the same way. That basically means it can't have operations done to it when being passed as a template argument.

BengtGustafsson commented 1 year ago

Ok, so here mytype::name is known to be a UA, it is not in itself dependent?

In my mind, by ambiguating mytype::name you either make the * 2 impossible to parse, or the ambiguation has no effect, it is treated as a value anyhow, just as it would without the ambiguation.

As we clearly don't want to ambiguate UTPs just to produce parsing errors it seems that ambiguation can't have any effect (in this situation at least).

What my finding says, is that if you have a UTP/UA or a classical dependent name without disambiguation there is no change in the parsing, it always assumes a value. However, at time of instantiation, IF the expression contained only the dependent name or UTP/UA name AND the expression is parsed for a template-argument, THEN the compiler should not cause an error if the dependent-name or UTP/UA can be matched to the template-parameter even if it turned out to not be a value.