Search code examples
c++c++14cpp-core-guidelines

gsl::span<T> and gsl::span<const T> overloads are ambigous


C++ Core Guidelines promotes the practice of using span.

The problem is with const and mutable ranges. This is what I tried to do:

auto foo(gsl::span<int>);         // 1st
auto foo(gsl::span<const int>);   // 2nd

But they can't be called without explicit span cast/construct of the argument:

std::vector<int> v;
const std::vector<int> cv;
const std::vector<int>& crv = v;

// ambiguous
// want to call 1st
foo(v);

// ambiguous, although 1st is illegal (static_assert kicks in)
// want to call 2nd
foo(cv); // ambiguous
foo(crv); // ambiguous

What is the proper way to deal with this?

This seems like something that should be trivial, analog to const T& and T& overloads, yet it isn't (or I just don't see it).

Just to be on the same page, foo(gsl::span<int>{v}) is cumbersome, and I want to avoid it, leave the callers simple: foo(v).


I generalized the issue, but just in case this is an XY problem, this is what I actually try to do:

auto split(gsl::cstring_span<> str) -> std::vector<gsl::cstring_span<>>;
auto split(gsl::string_span<> str) -> std::vector<gsl::string_span<>>;

and want to be callable with [const] char *, [const] string, etc. arguments.


Solution

  • According to P0122R1 the relevant constructor of the span class is:

    template <class Container>
    constexpr span(Container& cont);
    

    So all your 3 examples are unfortunately ill-formed. The second one can be made legal by requiring that this constructor is removed from overload resolution unless Container::value_type& is convertible to span::value_type& the Container is compatible with the span.

    Even if we do that, I see no way to allow number 1 and 3, since both overloads require exactly one user-defined implicit conversion.

    The usual workaround is to add another level:

    template<class T>
    auto foo( T && x ) { return foo_impl( as_span(std::forward<T>(x) ) ); }
    
    auto foo_impl(gsl::span<int>);         // 1st
    auto foo_impl(gsl::span<const int>);   // 2nd
    

    Note that as_span is not in P0122R1, but it is implemented in the Microsoft GSL. It works because it inspects the type and return a span<typename Container::value_type>.