marack
marack

Reputation: 2066

How to write C++ library to work with any implementation of span<T>?

I'm writing an I/O library where the user needs to supply blocks of memory to be read from or written to. Having my library accept a span<T> seems the most natural fit since:

Unfortunately there are competing implementations of span<T> in Boost, GSL and the standard library (as of C++20). The interface of these implementations are compatible, and from the user perspective it should not matter which one they use.

How can I code my I/O functions so that they work with any of the various implementations of span?

The only approach I can think of at the moment is to include my own implementation of span which would be implicitly constructable from anything with a ::element_type, .data() and .size().

It is important that implicit conversions from containers still be supported so that the user can simply pass a std::vector. For example:

void read_data(span<float> data);

std::vector<float> foo(1024);
read_data(foo);

Upvotes: 0

Views: 478

Answers (2)

jan.sende
jan.sende

Reputation: 860

EDIT 3:

I will leave my old posts for anyone who is interested. However, I just found the obvious solution...

#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>

With this approach you can just use span everywhere and program as usual.

ORIGINAL:

One solution would be to use a template for the span with a default value. Like:

template <template<class,size_t> class Span>
void read_data(Span<float> data);

However, you can run into problems with the various libraries using different signature for their containers. Thus, the better way would be to do:

template <class Span>
void read_data(Span data);

You are missing the type of the values now, but you should be able to retrieve it with:

using T = typename Span::value_type

In addition, you might want to add some std::enable_if (or Concepts) to your functions, which checks if Span actually provides the member functions you are using.

In practice, all of the above can result in very noisy code. If you only have a simple case, the easier way might be to define span with a using declaration of the library user.

The above will probably not work well with the std::vector overloads because the type signatures are somewhat similar. You can solve this by providing explicit std::vector overloads which do the right casts and call the Span version.

EDIT:

From your comment I gather that you want to convert a unspecified type (any container) to a unspecified type (any span). This neither makes sense nor is possible. You will need to define one of the types somewhere.

That being said, you could write conversion independent code and let the user provide the actual conversion. Like:

template <class Span>
void read_data_impl(Span data);

template <class Container>
void read_data(Container data)
{
   read_data_impl(convert(data));
}

The user would then need to supply:

template <class Container>
auto convert(Container& data)
{
    return USERSPAN(data);
}

EDIT 2:

There was a bug in my code. The data in the convert function has to be passed by reference. In addition, it is probably helpful if you pass allow passing a T for the container value by hand. Thus you end up with:

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));
}

And for conversion:

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);
}

The user then has to supply the following function:

template <class T, class Container>
auto convert_impl(Container& data)
{
    return USERSPAN<T>(data);
}

Upvotes: 0

Jarod42
Jarod42

Reputation: 217583

You might have a config step for user to build your library (or just a config.h for header library only):

Something like:

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

And then use lib_span<T> in your code.

Upvotes: 1

Related Questions