【问题标题】:Generic function using std::span doesn't compile使用 std::span 的通用函数无法编译
【发布时间】:2021-05-13 23:16:12
【问题描述】:

我想创建一个带有std::spanisIn 函数。

这是我的尝试:

#include <span>

template <typename T1, typename T2>
bool isIn(const T1& x, std::span<const T2> v)
{
    for (const T2& e : v)
        if (e == x)
            return true;
    return false;
}

// this one would work, but I want my function to be generic
/*bool isIn(int x, std::span<const int> v)
{
    for (int e : v)
        if (e == x)
            return true;
    return false;
}*/

int main()
{
    const int v[] = {1, 2, 3, 4};
    isIn(2, v); // I want this, but doesn't compile
    //isIn(2, std::span<const int>(v)); // this works fine
}

如你所见,我可以通过这个转换来解决问题:

isIn(2, std::span<const int>(v));

但这很冗长,我想做这样的事情:

isIn(2, v);

有什么方法可以实现吗?

https://godbolt.org/z/czTs83

【问题讨论】:

  • isIn&lt;int, int&gt;(2,v); 工作了吗?
  • 你有什么理由首先使用 std::span 吗?没有它,代码将正常工作。 godbolt.org/z/TqEzjn
  • @AdrianMaire 是的
  • @Arpegius 哇,我没想到 ranged for 也适用于 C 数组。谢谢!
  • @tuket 您可以通过其他方式获取参数。

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


【解决方案1】:

这是您的代码的 版本。

首先我们从两个概念开始; is_spanspannable:

template<class T>
concept is_span = requires(T& a) {
    { std::span(a) } -> std::same_as<T>;
};
template<class T>
concept spannable = requires(T& a) {
    { std::span(a) };
} && !is_span<T>;

spannable 可以推导出为 span 而不是一个。

然后我们写两个重载:

constexpr bool isIn(const auto& x, is_span auto const& v)
{
    for (const auto& e : v)
        if (e == x)
            return true;
    return false;
}

constexpr bool isIn(const auto& x, spannable auto const& v)
{
    return isIn(x, std::span(v));
}

使用新语法。

然后我们再添加一个重载:

template<std::size_t N>
constexpr bool isIn(const auto& x, auto const(& v)[N])
{
    return isIn(x, std::span(v));
}

允许这种美味的语法:

static_assert( isIn( 7, {1,2,3,4,5,6,7} ));

Live example

现在你要做的就是让它成为中缀。

那个“悲哀”的意思

static_assert( isIn('\0', "hello") );

是真的,因为"hello" 是一个数组,最后包含一个'\0'

template<class T>
constexpr bool isIn(const auto& x, std::initializer_list<T> il)
{
    return isIn(x, std::span(il));
}
template<std::size_t N>
constexpr bool isIn(char x, char const(& v)[N])
{
    return isIn(x, std::span(v, v+N-1));
}

Live example.

【讨论】:

    【解决方案2】:

    模板扣减无转化/推广,

    所以const int (&amp;)[4] 不能推导出为std::span&lt;const int /*, 4*/&gt;

    您可能仍然提供重载来自己进行转换(注意避免无限递归调用):

    template <typename T1, typename T2, std::size_t N>
    bool isIn(const T1& x, std::span<const T2, N> v)
    {
        // return std::find(std::begin(v), std::end(v), x) != std::end(v);
        for (const T2& e : v) {
            if (e == x) {
                return true;
            }
        }
        return false;
    }
    
    template <typename T, typename C>
    bool isIn(const T& x, const C&c)
    {
        return isIn(x, std::span(c)); // Use CTAD for the conversion.
    }
    

    但这里不需要std::span

    template <typename T, typename C>
    bool isIn(const T& x, const C& c)
    {
        // return std::find(std::begin(c), std::end(c), x) != std::end(c);
        for (const auto& e : c) {
            if (e == x) {
                return true;
            }
        }
        return false;
    }
    

    【讨论】:

    【解决方案3】:

    没有必要在像这样的泛型函数中使用std::span

    template <typename T1, typename T2>
    bool isIn(T1&& x, T2 &&v) // always use universal references
    { 
        for (auto&& e : v)
            if (e == x)
                return true;
        return false;
    }
    
    int main()
    {
        const int v[] = {1, 2, 3, 4};
        isIn(2, v); // T2 is const &int[4]
        isIn(2, std::span<const int>{v}); // T2 is span<const int>
        isIn(2, std::list<int>(std::begin(v),std::end(v)) ); // T2 is list<int>
           // but list cannot be a span!
    
        isIn('l',"blablalba"); //works too, T2 is const &char[9]
    
        std::string_view s = "blablalba";
        isIn('l',s); // T2 is std::string_view&
    
    }
    

    这种方式适用于定义了 std::begin 和 std::end 的任何类型。

    【讨论】:

      【解决方案4】:

      这个实现涵盖了我所有的用例。我认为它可能对其他人有用:

      #include <stdio.h>
      #include <initializer_list>
      #include <string_view>
      
      template <typename T1, typename T2>
      bool isIn(const T1& x, const T2 &v)
      { 
          printf("generic\n");
          for (auto& e : v)
              if (e == x)
                  return true;
          return false;
      }
      
      bool isIn(char x, const char* v)
      {
          // NOTE: we have this specialization because we don't want to consider the null terminator
          printf("str\n");
          return isIn(x, std::string_view(v));
      }
      
      template <typename T1, typename T2>
      bool isIn(const T1& x, std::initializer_list<T2> v)
      { 
          printf("initializer_list\n");
          for (auto& e : v)
              if (e == x)
                  return true;
          return false;
      }
      
      int main()
      {
          const int v[] = {1, 2, 3, 4};
          isIn(2, v); // generic
          isIn(2, {1, 2, 3, 4}); // initializer_list
          isIn('l', "blabla"); // str
      }
      

      https://godbolt.org/z/d3nW73

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-10-03
        • 2021-04-29
        • 2023-03-15
        • 1970-01-01
        • 1970-01-01
        • 2018-09-12
        相关资源
        最近更新 更多