The following code uses multiple parameter packs to define a variadic template accumulator function that accepts any numeric type or pointer to numeric type:
// Compile with --std=c++20
#include <type_traits>
template <typename T>
concept number = std::is_arithmetic_v<T>
&& !std::is_pointer_v<T>;
template <typename T>
concept pointer = std::is_arithmetic_v<std::remove_pointer_t<T>>
&& std::is_pointer_v<T>;
double foo ()
{return 0;}
double foo (pointer auto p0)
{return *p0;}
double foo (pointer auto p0,
pointer auto ... ps)
{return *p0 + foo (ps ...);}
double foo (number auto n0,
pointer auto ... ps)
{return n0 + foo (ps ...);}
double foo (number auto n0,
number auto ... ns, /* <---- THIS LINE */
pointer auto ... ps)
{return n0 + foo (ns ..., ps ...);}
int main()
{
float f = 3.;
unsigned u = 4;
foo (); // Compiles
foo (1); // Compiles
foo (&f); // Compiles
foo (1, &f); // Compiles
foo (1, &f, &u); // Compiles
foo (&f, &u); // Compiles
foo (1, 2.); // Error!
foo (1, 2., &f); // Error!
foo (1, 2., &f, &u); // Error!
}
The error triggers when there is more than one argument of type number.
It looks like when there are multiple parameter packs, the compiler packs all arguments in the last pack, instead of refering to the constraints to define which argument belongs to which parameter pack.
Is this a limitation of the language? Are multiple parameter packs meant to be used in some other way? Is there any workaround to make it work?
tested in clang and GCC
Update: Solved!
Solution: Use a single parameter pack, do not constrain the parameter pack, and constrain the type of the parameters on a one-by-one basis.
// Compile with --std=c++20
#include <type_traits>
template <typename T>
concept number = std::is_arithmetic_v<T>;
template <typename T>
concept pointer = std::is_arithmetic_v<std::remove_pointer_t<T>>
&& std::is_pointer_v<T>;
double foo ()
{return 0;}
double foo (pointer auto p0)
{return *p0;}
double foo (pointer auto p0,
pointer auto ... ps)
{return *p0 + foo (ps ...);}
template <typename ... N_P>
double foo (number auto n0,
N_P ... ps)
{return n0 + foo (ps ...);}
int main()
{
float f = 3.;
unsigned u = 4;
foo (); // Compiles
foo (1); // Compiles
foo (&f); // Compiles
foo (1, &f); // Compiles
foo (1, &f, &u); // Compiles
foo (&f, &u); // Compiles
foo (1, 2.); // Good!
foo (1, 2., &f); // Good!
// foo (1, &f, 2.); // Does not compile (Good!)
return foo (1, 2., &f, &u); // Good!
}