max66
max66

Reputation: 66210

template, well formedness and zero pack length rule

From the accepted answer of a previous question I've discovered a rule I didn't know about templates and well formedness

The program is ill-formed, no diagnostic required, if:

  • [...]
  • every valid specialization of a variadic template requires an empty template parameter pack, or
  • [...]

According this rule (if I understand correctly), the following template function is ill-formed

template <typename ... Ts>
int foo (std::tuple<Ts...> const &)
 { return std::get<sizeof...(Ts)>(std::tuple<int>{42}); }

because the only valid specialization require and empty Ts... parameter pack.

But (maybe because I don't know English very well) I'm not sure to understand this rule in case of a template with two ore more parameter packs.

I mean... the following foo() function

#include <tuple>
#include <iostream>

template <typename ... Ts, typename ... Us>
int foo (std::tuple<Ts...> const &, std::tuple<Us...> const &)
 { return std::get<sizeof...(Ts)+sizeof...(Us)-1U>(std::tuple<int>{42}); }

int main ()
 {
   auto t0 = std::tuple<>{};
   auto t1 = std::tuple<int>{0};

   //std::cout << foo(t0, t0) << std::endl; // compilation error
   std::cout << foo(t0, t1) << std::endl;   // print 42
   std::cout << foo(t1, t0) << std::endl;   // print 42
   //std::cout << foo(t1, t1) << std::endl; // compilation error
 }

is well-formed or ill-formed?

Because a valid specialization of it require that Ts... or Us... is empty (and that the other parameter pack is exactly of size 1).

The rule should be interpreted in the sense that a programm is ill-formed if there is an empty parameter pack that must be ever empty (so my example should be well formed because both parameter packs can be not empty) or in the sense that is ill-formed if in every specialization there is at least an empty parameter pack, not necessarily the same in every specialization (so my example should be ill-formed) ?

Upvotes: 5

Views: 137

Answers (1)

Brian Bi
Brian Bi

Reputation: 119239

I think the intent of the rule has not been communicated clearly by the standard wording and perhaps never will.

The "no valid specialization" rule is intended to allow compilers to perform early diagnosis of constructs that are grammatically valid but can be seen to be semantically nonsensical without having to instantiate the template. Here's an example:

template <int x>
void foo() {
    typedef double D;
    x.~D();
}

There's nothing grammatically wrong with this. Semantically, this is trying to destroy a value of type int as if it were of type double, which is of course not allowed. If you instantiate foo then the compiler is allowed to diagnose it, but isn't required to. It seems that Clang gives a diagnostic, while GCC doesn't.

In the same vein, the intent of the rule about template parameter packs is that if the pattern for a pack expansion is semantically nonsensical, then the compiler is allowed to diagnose it early. A variant of the above example illustrates this:

template <int... x>
void foo() {
    typedef double D;
    (x.~D(), ...);
}

The only way this can be well-formed is if there are 0 instances of x.~D() in the expansion; even 1 will always be ill-formed. So the idea here is that the compiler is allowed (but not required) to diagnose the ill-formed pattern even though it's possible to get a well-formed specialization by supplying an empty pack that results in 0 elements in the expansion.

(Again, GCC is ok with this, and even lets you call foo<>(). Clang diagnoses it even when it's not instantiated.)

Based on that, the rule when there are two parameter packs should be:

  • If the only thing you do with them is use sizeof..., then the rule about every valid specialization requiring an empty pack shouldn't even apply at all. It's not in the "spirit" of the current rule.
  • If you expand them individually, then the rule should be that if there exists at least one pack such that pack must be empty in order to get a valid instantiation then the program is IFNDR. That means the compiler is allowed to diagnose either ill-formed pattern.
  • If you do a nested expansion, i.e., you have a pattern that contains one of the packs, you expand it, and then that's part of a larger expression that also contains the second pack, and that is also expanded (so the result has MN elements, where M is the number of elements in the first pack and N is the number of elements in the second pack) then the rule should be that if every valid instantiation requires at least one pack to be empty then the program is IFNDR.

Upvotes: 1

Related Questions