Как написать библиотеку C++ для работы с любой реализацией span<T>?
Я пишу библиотеку ввода / вывода, в которой пользователю необходимо предоставить блоки памяти для чтения или записи. Моя библиотека принимает span<T>
кажется наиболее естественным, так как:
- Это не навязывает пользователю выбор контейнера. Они могут использовать сырые указатели,
std::vector
или любой другой контейнер с непрерывным хранением. - Это позволяет мне гарантировать безопасный доступ к памяти, так как я знаю размер буфера.
К сожалению, есть конкурирующие реализации span<T>
в Boost, GSL и стандартной библиотеке (по состоянию на C++20). Интерфейс этих реализаций совместим, и с точки зрения пользователя не должно иметь значения, какой из них они используют.
Как я могу кодировать свои функции ввода / вывода, чтобы они работали с любой из различных реализаций span
?
Единственный подход, который я могу придумать на данный момент, - это включить мою собственную реализацию span
который был бы неявно конструируемым из всего с ::element_type
, .data()
а также .size()
,
Важно, чтобы неявные преобразования из контейнеров по-прежнему поддерживались, чтобы пользователь мог просто передать std::vector
, Например:
void read_data(span<float> data);
std::vector<float> foo(1024);
read_data(foo);
2 ответа
У вас может быть шаг конфигурации для пользователя, чтобы собрать вашу библиотеку (или просто 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>
в вашем коде.
РЕДАКТИРОВАТЬ 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:
В моем коде была ошибка. data
в функцию convert необходимо передать ссылку. Кроме того, это, вероятно, полезно, если вы передаете разрешить передачу 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);
}