【问题标题】:Multiple concept-constrained parameter packs in variadic function in C++20 does not accept arguments in the first parameter packC ++ 20中可变参数函数中的多个概念约束参数包不接受第一个参数包中的参数
【发布时间】:2020-11-15 22:57:38
【问题描述】:

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

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

【问题讨论】:

  • 一个类型不能同时是算术和指针,所以std::is_arithmetic_v&lt;T&gt; &amp;&amp; !std::is_pointer_v&lt;T&gt;是多余的。
  • "多个参数包是不是打算以其他方式使用?" 广义上讲,多个函数参数包根本不打算使用 i>.

标签: c++ overloading variadic-templates c++20 c++-concepts


【解决方案1】:

函数参数包的推论只发生在参数列表中的最后一个包。所有其他包都是considered a non-deduced context:

未推断的上下文是:

...

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

概念不会影响这一点。您不能将概念用作使第一包可推论的一种方式。

无论如何,只要有一个可以是算术类型或指向算术类型的指针、折叠表达式和single function to distinguish which from which 的概念会容易得多:

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

是的,您将能够在数字之前传递指针。但是,对于您想要做的任何事情,这不是更好的界面吗?

【讨论】:

  • 由于函数的语义,我必须保留参数的顺序。
  • 我想知道为什么多个推导的参数包不会触发编译器错误
  • @Arjonais:因为标准说只有最后一个被推断出来。其他的都是empty,这对于包来说是完全有效的状态。
  • @Arjonais:“由于函数的语义,我必须保留参数的顺序。”为什么?如果有超出个人偏好的特定需求,那么您应该使用一个用例,其中能够保留该概念实际上很重要。
  • @Arjonais: "参数包 X 始终为空" 但它始终为空;您可以明确指定一些第一个包的值。
猜你喜欢
  • 1970-01-01
  • 2022-10-21
  • 1970-01-01
  • 1970-01-01
  • 2022-01-08
  • 2021-10-22
  • 2018-05-31
  • 1970-01-01
  • 2012-01-31
相关资源
最近更新 更多