使用 std::span 的通用函数无法编译
Generic function using std::span doesn't compile
我想制作一个 isIn
函数,它接受 std::span
。
这是我的尝试:
#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);
有什么方法可以实现吗?
不需要在像这样的通用函数中使用 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 的任何类型。
模板推导没有conversion/promotion,
所以 const int (&)[4]
不能推导为 std::span<const int /*, 4*/>
.
您可能仍会提供重载来自己进行转换(注意避免无限递归调用):
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;
}
这个实现涵盖了我所有的用例。我虽然它可能对其他人有用:
#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
}
这是您的代码的 c++20 版本。
首先我们从两个概念开始; is_span
和 spannable
:
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} ));
现在你所要做的就是让它成为中缀。
那个“悲哀”的意思
static_assert( isIn('[=14=]', "hello") );
是正确的,因为 "hello"
是一个末尾包含 '[=21=]'
的数组。
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));
}
我想制作一个 isIn
函数,它接受 std::span
。
这是我的尝试:
#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);
有什么方法可以实现吗?
不需要在像这样的通用函数中使用 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 的任何类型。
模板推导没有conversion/promotion,
所以 const int (&)[4]
不能推导为 std::span<const int /*, 4*/>
.
您可能仍会提供重载来自己进行转换(注意避免无限递归调用):
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;
}
这个实现涵盖了我所有的用例。我虽然它可能对其他人有用:
#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
}
这是您的代码的 c++20 版本。
首先我们从两个概念开始; is_span
和 spannable
:
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} ));
现在你所要做的就是让它成为中缀。
那个“悲哀”的意思
static_assert( isIn('[=14=]', "hello") );
是正确的,因为 "hello"
是一个末尾包含 '[=21=]'
的数组。
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));
}