如何编写 C++ 库以与 span<T> 的任何实现一起工作?
How to write C++ library to work with any implementation of span<T>?
我正在编写一个 I/O 库,用户需要在其中提供要读取或写入的内存块。让我的图书馆接受 span<T>
似乎是最自然的选择,因为:
- 它不会将容器选择强加给用户。他们可以使用原始指针,
std::vector
,或任何其他具有连续存储的容器。
- 它允许我确保内存访问是安全的,因为我知道缓冲区的大小。
不幸的是,在 Boost、GSL 和标准库(自 C++20 起)中存在 span<T>
的竞争实现。这些实现的接口是兼容的,从用户的角度来看,使用哪一个应该无关紧要。
我如何编写我的 I/O 函数,以便它们与 span
的各种实现一起工作?
目前我能想到的唯一方法是包括我自己的 span
实现,它可以从任何具有 ::element_type
、.data()
和 .size()
.
重要的是仍然支持来自容器的隐式转换,以便用户可以简单地传递 std::vector
。例如:
void read_data(span<float> data);
std::vector<float> foo(1024);
read_data(foo);
编辑 3:
我会把我的旧帖子留给有兴趣的人。但是,我刚刚找到了明显的解决方案...
#include <user_span>
//dive into your namespace to avoid name collisions
namespace YOUR_LIB
{
//supply custom type
template <class T, SIZET size = DEFAULT>
using span = ::user_span<T, size>;
}
//actually include the library
#include <your_lib>
通过这种方法,您可以在任何地方使用 span 并像往常一样编程。
原文:
一种解决方案是使用具有默认值的跨度模板。喜欢:
template <template<class,size_t> class Span>
void read_data(Span<float> data);
但是,您可能 运行 遇到使用不同容器签名的各种库的问题。因此,更好的方法是:
template <class Span>
void read_data(Span data);
您现在缺少值的类型,但您应该能够通过以下方式检索它:
using T = typename Span::value_type
此外,您可能希望向您的函数添加一些 std::enable_if
(或概念),以检查 Span
是否实际提供了您正在使用的成员函数。
在实践中,上述所有情况都会导致非常嘈杂的代码。如果您只有一个简单的案例,更简单的方法可能是使用库用户的 using
声明来定义跨度。
以上内容可能无法很好地与 std::vector
重载一起使用,因为类型签名有些相似。您可以通过提供明确的 std::vector
重载来解决这个问题,这些重载可以进行正确的转换并调用 Span
版本。
编辑:
从您的评论中我得知您想将未指定类型(任何容器)转换为未指定类型(任何跨度)。这既不合理也不可能。您将需要在某处定义其中一种类型。
也就是说,您可以编写独立于转换的代码并让用户提供实际的转换。喜欢:
template <class Span>
void read_data_impl(Span data);
template <class Container>
void read_data(Container data)
{
read_data_impl(convert(data));
}
然后用户需要提供:
template <class Container>
auto convert(Container& data)
{
return USERSPAN(data);
}
编辑 2:
我的代码中有错误。 convert 函数中的 data
必须通过引用传递。此外,如果您通过允许手动为容器值传递 T
可能会有所帮助。因此你最终得到:
template <class Span>
void read_data_impl(Span data);
template <class Container>
void read_data(Container data)
{
read_data_impl(convert(data));
}
template <class T, class Container>
void read_data(Container data)
{
read_data_impl(convert<T>(data));
}
并进行转换:
template <class T, class Container>
auto convert(Container& data)
{
return convert_impl<T>(data);
}
template <class Container>
auto convert(Container& data)
{
return convert_impl<typename Container::value_type>(data);
}
然后用户必须提供以下功能:
template <class T, class Container>
auto convert_impl(Container& data)
{
return USERSPAN<T>(data);
}
您可能有一个配置步骤供用户构建您的库(或者仅 config.h 用于头库):
类似于:
config.h:
// include guard omitted.
#if defined(SPAN_TYPE) // To allow custom span
template <typename T> using lib_span = SPAN_TYPE<T>;
#elif defined(USE_STD_SPAN)
template <typename T> using lib_span = ::std::span<T>;
#elif defined(USE_BOOST_SPAN)
template <typename T> using lib_span = ::boost::span<T>;
// ...
#else
# error "No span provided"
#endif
然后在您的代码中使用 lib_span<T>
。
我正在编写一个 I/O 库,用户需要在其中提供要读取或写入的内存块。让我的图书馆接受 span<T>
似乎是最自然的选择,因为:
- 它不会将容器选择强加给用户。他们可以使用原始指针,
std::vector
,或任何其他具有连续存储的容器。 - 它允许我确保内存访问是安全的,因为我知道缓冲区的大小。
不幸的是,在 Boost、GSL 和标准库(自 C++20 起)中存在 span<T>
的竞争实现。这些实现的接口是兼容的,从用户的角度来看,使用哪一个应该无关紧要。
我如何编写我的 I/O 函数,以便它们与 span
的各种实现一起工作?
目前我能想到的唯一方法是包括我自己的 span
实现,它可以从任何具有 ::element_type
、.data()
和 .size()
.
重要的是仍然支持来自容器的隐式转换,以便用户可以简单地传递 std::vector
。例如:
void read_data(span<float> data);
std::vector<float> foo(1024);
read_data(foo);
编辑 3:
我会把我的旧帖子留给有兴趣的人。但是,我刚刚找到了明显的解决方案...
#include <user_span>
//dive into your namespace to avoid name collisions
namespace YOUR_LIB
{
//supply custom type
template <class T, SIZET size = DEFAULT>
using span = ::user_span<T, size>;
}
//actually include the library
#include <your_lib>
通过这种方法,您可以在任何地方使用 span 并像往常一样编程。
原文:
一种解决方案是使用具有默认值的跨度模板。喜欢:
template <template<class,size_t> class Span>
void read_data(Span<float> data);
但是,您可能 运行 遇到使用不同容器签名的各种库的问题。因此,更好的方法是:
template <class Span>
void read_data(Span data);
您现在缺少值的类型,但您应该能够通过以下方式检索它:
using T = typename Span::value_type
此外,您可能希望向您的函数添加一些 std::enable_if
(或概念),以检查 Span
是否实际提供了您正在使用的成员函数。
在实践中,上述所有情况都会导致非常嘈杂的代码。如果您只有一个简单的案例,更简单的方法可能是使用库用户的 using
声明来定义跨度。
以上内容可能无法很好地与 std::vector
重载一起使用,因为类型签名有些相似。您可以通过提供明确的 std::vector
重载来解决这个问题,这些重载可以进行正确的转换并调用 Span
版本。
编辑:
从您的评论中我得知您想将未指定类型(任何容器)转换为未指定类型(任何跨度)。这既不合理也不可能。您将需要在某处定义其中一种类型。
也就是说,您可以编写独立于转换的代码并让用户提供实际的转换。喜欢:
template <class Span>
void read_data_impl(Span data);
template <class Container>
void read_data(Container data)
{
read_data_impl(convert(data));
}
然后用户需要提供:
template <class Container>
auto convert(Container& data)
{
return USERSPAN(data);
}
编辑 2:
我的代码中有错误。 convert 函数中的 data
必须通过引用传递。此外,如果您通过允许手动为容器值传递 T
可能会有所帮助。因此你最终得到:
template <class Span>
void read_data_impl(Span data);
template <class Container>
void read_data(Container data)
{
read_data_impl(convert(data));
}
template <class T, class Container>
void read_data(Container data)
{
read_data_impl(convert<T>(data));
}
并进行转换:
template <class T, class Container>
auto convert(Container& data)
{
return convert_impl<T>(data);
}
template <class Container>
auto convert(Container& data)
{
return convert_impl<typename Container::value_type>(data);
}
然后用户必须提供以下功能:
template <class T, class Container>
auto convert_impl(Container& data)
{
return USERSPAN<T>(data);
}
您可能有一个配置步骤供用户构建您的库(或者仅 config.h 用于头库):
类似于:
config.h:
// include guard omitted.
#if defined(SPAN_TYPE) // To allow custom span
template <typename T> using lib_span = SPAN_TYPE<T>;
#elif defined(USE_STD_SPAN)
template <typename T> using lib_span = ::std::span<T>;
#elif defined(USE_BOOST_SPAN)
template <typename T> using lib_span = ::boost::span<T>;
// ...
#else
# error "No span provided"
#endif
然后在您的代码中使用 lib_span<T>
。