Как написать библиотеку 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);
}
Другие вопросы по тегам