C++20中变元函数中的多个概念约束参数包不接受第一个参数包中的参数



以下代码使用多个参数包来定义一个可变模板累加器函数,该函数接受任何数字类型或指向数字类型的指针:

// 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!
}

当存在多个类型为number的参数时,会触发错误。

看起来,当有多个参数包时,编译器会将所有参数打包在最后一个包中,而不是引用约束来定义哪个参数属于哪个参数包。

这是语言的限制吗?多个参数包是否意味着以其他方式使用?有什么变通办法让它发挥作用吗?

在clang和GCC 中测试

更新:已解决

解决方案:使用单个参数包,不约束参数包,并逐个约束参数的类型。

// 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!
}

函数参数包的推导仅发生在参数列表中的最后一个包中。所有其他包都被视为非推导上下文:

非推导上下文为:

不出现在参数声明列表末尾的函数参数包。

概念不会影响这一点。你不能用概念来推断第一个包。

在任何情况下,只要有一个概念就容易得多,它可以是一个算术类型或指向算术类型的指针、一个折叠表达式和一个单独的函数来区分哪个:

#include <type_traits>
template <typename T>
concept number = std::is_arithmetic_v<T>; //Pointers aren't arithmetic types.
template <typename T>
concept ptr_to_num =
std::is_pointer_v<T> &&
number<std::remove_pointer_t<T>>;
template<typename T>
concept ptr_to_num_or_num =
number<T> || ptr_to_num<T>;
template<ptr_to_num_or_num T>
double dereference(T p)
{
if constexpr(ptr_to_num<T>)
return *p;
else
return p;
}
template<ptr_to_num_or_num ...Args>
double foo(Args ...args)
{
return (0.0 + ... + dereference(args));
}
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!
}

是的,你将能够在数字之前传递指针。但是,无论你想做什么,这不是一个更好的界面吗?

最新更新