【问题标题】:How to use user defined concept as a template type of std::span?如何使用用户定义的概念作为 std::span 的模板类型?
【发布时间】:2021-07-25 01:49:09
【问题描述】:

我想将我的用户定义概念用作 std::span 的模板类型,但模板参数推导无法按预期工作。当我尝试将“char”的“std::array”传递给模板函数时;编译器显示错误“error: no matching function for call to 'print'”并在我将鼠标悬停在模板定义上时警告我作为 ”注意:候选模板被忽略:无法匹配'span' 对 'array'".

这里是概念定义和函数模板:

#include <concepts>
#include <span>

template <typename  T>
concept OneByteData = sizeof(T) == 1;

template<OneByteData T>
void print(std::span<const T> container)
{
    for(auto element : container)
    {
        //Do Some Work
    }
}

而且用户代码没有按我的预期工作:

int main()
{
    std::array<char, 6> arr = {1, 2, 3, 4, 5, 6};
    print(arr);
    return 0;
}

有效且不产生错误的用户代码:

int main()
{
    std::array<char, 6> arr = {1, 2, 3, 4, 5, 6};
    print<char>(arr);
    return 0;
}

有没有办法在不专门指定数组类型的情况下调用这个模板函数。我应该如何更改模板函数定义以使函数按照我提到的方式调用(@​​987654326@)?

编辑: 我希望能够利用 std::span 的优势,并能够使用 std::array、std::vector 和纯 C 样式调用模板函数数组。

【问题讨论】:

    标签: c++ templates c++20 c++-concepts std-span


    【解决方案1】:

    一种可能的解决方案是接收(并推断)一个泛型类型,然后检查它是否可转换为大小为 1 的 element_typestd::span

    我是说

    template <typename  T>
    concept OneByteData = sizeof(T) == 1;
    
    template <typename T>
    void print (T container) 
       requires OneByteData<typename decltype(std::span{container})::element_type>
    {
      std::span cnt {container};
      
        for(auto element : cnt )
        {
            //Do Some Work
        }
    }
    
    // extra print for C-style cases
    template <typename T, std::size_t N>
    void print (T(&arr)[N])
    { print(std::span{arr}); }
    

    另一种可能的解决方案是print(),类似于您的原始解决方案,接收std::span 并进行概念检查,以及几个额外的print()(一个特定于C 样式数组)转换推导的输入std::span

    template <typename  T>
    concept OneByteData = sizeof(T) == 1;
    
    template<OneByteData T, std::size_t N>
    void print(std::span<T, N> container)
    {
        for(auto element : container)
        {
            //Do Some Work
        }
    }
    
    template <typename T, std::size_t N>
    void print (T(&arr)[N])
    { print(std::span{arr}); }
    
    template <typename T>
    void print (T container)
     { print(std::span{container}); }
    

    【讨论】:

    • 谢谢@max66。您共享的代码可与 std::array 和 std::vector 一起使用,但是当我尝试传递纯 C 样式的数组时,它会产生编译器错误。 &lt;source&gt;:31:5: error: no matching function for call to 'print' print(rawArray); ^~~~~ &lt;source&gt;:11:6: note: candidate template ignored: substitution failure [with T = unsigned char *]: no viable constructor or deduction guide for deduction of template arguments of 'span' void print (T container)
    • @sakcakoca - 不幸的是,C 风格的数组通常被推导为指针,因此推导的类型会丢失维度,因此无法使用 CTAD 将推导的类型转换为 std::span。我建议添加一个中间 print(),专门为 C 风格的数组设计(也推导出大小),以所需的跨度调用最终的 print()。查看修改后的答案。
    • @sakcakoca - 添加了建议解决方案的变体。
    • 第一个扩展支持 C 样式数组的解决方案就像我想要的那样工作,它为 std::arraystd:: vector 和符合要求的 C 样式数组产生正确的行为的OneByteData谢谢。但是,第二个在语法上看起来更简单,我想尝试使用它,但是当我在客户端代码中传递 std::array&lt;int, N&gt; 时它不会产生任何编译错误,它只是在运行时抛出异常。它应该产生编译错误,因为int 不符合OneByteData 概念的要求。
    • 在第一个解决方案中,使用value_type 而不是element_type 怎么样。哪个更正确?
    【解决方案2】:

    From my understanding这就是你实际使用它的方式:

    // Second template parameter for size
    template<OneByteData T, std::size_t N>
    void print(std::span<T, N> container) {
      // Do something
      return;
    }
    
    // Create a span from a plain array
    print(std::span{arr});
    

    Try it here.


    如果你不希望你的用户自己做,你可以

    • print编写一个重载,处理从std::arraystd::span的转换,例如(Wandbox)

      template<OneByteData T, std::size_t N>
      void print(std::array<T,N> const& container) {
        return print<T const>(std::span{container});
      }
      
    • 否则,您可以重写print 的接口 以采用任何容器,使用std::enable_if 或用户 max66 等概念强制对基础元素类型进行约束已提议并在内部将此通用容器转换为std::span

    • 对于一个,你可以编写一个模板推导指南来决定应该使用哪个构造函数(Wandbox

      template <typename T, std::size_t N>
      Print(std::array<T,N>) -> Print<T,N>;
      

    编辑

    对于像您在 cmets 中讨论的那样的运算符,我实际上会使用 模板化函数和模板化运算符的组合。函数append 使用OneByteData 数据类型的通用std::span 完成工作,而模板化运算符将允许的数据类型转换为std::span 并调用该函数。 OneByteData&lt;typename decltype(std::span{cont})::element_type&gt; 确保可以将数据结构转换为具有正确数据类型的std::span。您可以为其添加额外的或不同的约束,或者将这些约束结合到您自己的概念中。

    template<OneByteData T, std::size_t N>
    void append(std::span<T,N> const& sp) {
      // Do something
    }
    
    template <typename Cont>
    MyString& operator += (Cont const& cont) 
    requires OneByteData<typename decltype(std::span{cont})::element_type> {
      this->append(std::span{cont});
      return *this;
    }
    

    试试herehere

    【讨论】:

    • 它可能会按照您提到的方式工作,但我希望客户端代码能够调用模板函数而无需任何强制转换或创建新类型。如果可能,客户端代码应该类似于print(arr)。 @2b-t
    • @sakcakoca 我明白了。它是一个没有类的函数,对吧?否则你可以为它指定一个扣除指南。
    • 其实模板函数是一个类的成员函数。它实际上是一个operator+= 函数,我应该能够传递std::array&lt;char&gt;std::array&lt;unsigned char&gt;std::array&lt;std::byte&gt;std::vector&lt;char&gt;std::vector&lt;unsigned char&gt;std::vector&lt;std::byte&gt;plain C-style arrays of char, unsigned char, std::byte 等。但我简化了示例代码关注我真正的问题。
    • 在你提到的第二个解决方案中,我想我应该为std::array&lt;T,N&gt;std::vector&lt;T&gt; 和普通 C 样式数组编写 print 函数的所有重载。
    • @sakcakoca 嗯,我明白了......除了编写一个函数add(std::span) 之外,我想不出另一种解决方案,它包含实现并分别为这些类型定义运算符,它将所有上述数据类型为std::span。为了减少代码,您可以编写一个使用std::enable_if 的模板化运算符,以便它仅适用于所有上述数据类型。
    猜你喜欢
    • 2010-11-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-01
    相关资源
    最近更新 更多