cplusplus / CWG

Core Working Group
23 stars 7 forks source link

[temp.arg.explicit] p9 Extend the sequence of template arugments when the template parameter pack is a pack expansion #564

Open xmh0511 opened 1 week ago

xmh0511 commented 1 week ago

Full name of submitter (unless configured in github; will be published with the issue): Jim X

Consider this example:

#include <iostream>
template<class ,std::size_t N>
concept True = true;
template<std::size_t ... N>
struct C{
    template<True<N>... T>
    static void fun(T... t){}
};

int main(){
    C<0,1,2>::fun(1,2,3,4,5);
}

[temp.arg.explicit] p9 says:

Template argument deduction can extend the sequence of template arguments corresponding to a template parameter pack, even when the sequence contains explicitly specified template arguments.

[temp.deduct.call] p1 says:

For a function parameter pack that occurs at the end of the parameter-declaration-list, deduction is performed for each remaining argument of the call, taking the type P of the declarator-id of the function parameter pack as the corresponding function template parameter type. Each deduction deduces template arguments for subsequent positions in the template parameter packs expanded by the function parameter pack.

In this example, True<N>... T declares a template parameter pack and T... t is a function parameter pack that expands the template parameter pack declared in the former.

[temp.variadic] p1 and p2

A template parameter pack is a template parameter that accepts zero or more template arguments.

A function parameter pack is a function parameter that accepts zero or more function arguments.

According to these rules, the example should be otherwise ok. However, major implementations reject this example and give different reasons.

GCC says:

mismatched argument pack lengths while expanding 'True<T, N>'

Clang says:

candidate function [with T = <int, int, int>] not viable: requires 3 arguments, but 5 were provided

Suggested Resolution

We either need to say

  1. The template parameter pack that is a pack expansion or a function parameter pack whose type is introduced by a template parameter pack that is a pack expansion is never expanded from deduction, or
  2. The number of elements in the pack expansion and the deduced one should be equivalent.
template<class ,std::size_t N>
concept True = true;
template<std::size_t ... N>
struct C{
    template<True<N>... T>
    static void fun(T... t){}

    template<True<N>... T>
    struct D{};
};

int main(){
    C<0,1,2>::fun(1,2,3,4,5);  // ill-formed
    C<0,1>::D<int, float,double>{};  // ill-formed
}
xmh0511 commented 1 week ago

A relevant example that causes implementations to have divergence is https://godbolt.org/z/6xq6n8z8Y, Clang accepts it while GCC reports a similar error

mismatched argument pack lengths while expanding 'e<infix, j>'

This example can be simplified to

#include <iostream>
template<class ,std::size_t N>
concept True = true;
template<std::size_t ... N>
struct C{
    template<True<N>... T, True<N>... U>
    static void fun(T... t,U...){}
};

int main(){
    C<0,1,2>::fun(1,2,3,4,5,6);
}

Clang is Ok while GCC thinks the remaining arguments should be used to deduce the template arguments for True<N>... T.

jensmaurer commented 1 week ago

Deduction can never change template arguments from an outer context (i.e. the arguments for "N" from the outer "C" template).

xmh0511 commented 1 week ago

Deduction can never change template arguments from an outer context (i.e. the arguments for "N" from the outer "C" template).

Never change can be simplified to "not perform", It seems to lack wording that specifies the deduction does not perform to the template parameter pack introduced by a template-parameter that is a pack expansion, nor to a function parameter whose type is such a pack.