kaba
kaba

Reputation: 735

std::span<const T> as parameter in function template

In the following library functions f and g, I use std::span<const T> to remind the user of the library of the contract that f and g will not modify the contents of the span. The user of the library holds std::span<int> a, which is convertible to std::span<const int>. Hence, the user can call g(a) as expected. However, the user cannot call f(a), since f is a function template and a is not of the required type; conversions do not apply here. Can you see a (sane) way to have f take in std::span<const T> while still accepting std::span<T>?

#include <span>

void g(const std::span<const int>& a) {}

template <typename T>
void f(const std::span<const T>& a) {}

int main()
{
    std::span<int> a;
    // OK
    g(a);
    // No match
    f(a);
    // OK
    f((std::span<const int>)a);
}

It is possible to add an overload like this:

template <typename T>
void f(const std::span<T>& a) {
    return f((std::span<const T>)a);
}

But I'm not sure if I count this as sane, since then I would be writing these overloads to every function template which takes in std::span<const T>.

EDIT: Not the same case, but if f also happens to take in another parameter which mentions T, then one can do the following:

template <typename T> using NoDeduction = std::type_identity_t<T>;

template <typename T>
void f(NoDeduction<std::span<const T>> a, const T& b) {}

After which f(a, 1); works. This is a partial solution. The original problem still remains.

EDIT 2: I was wondering whether the above technique works also when we include span's compile-time size:

template <typename T, std::size_t N>
void f(const std::span<NoDeduction<const T>, N>& a, const T& b) {}

Gcc 10.1 compiles it, MSVC 16.6.2 and Clang 10.0.0 don't. I filed bug reports for both MSVC and Clang.

Upvotes: 4

Views: 2022

Answers (2)

Davis Herring
Davis Herring

Reputation: 39898

You can generalize your overload to be a utility like std::as_const:

template<class T>
std::span<const T> const_span(std::span<T> s) {return s;}

The caller then has to decorate their calls with mutable spans, but in so doing indicates to the reader that no modification is allowed.

Another, unconventional workaround would be to make your function be a class and use deduction guides:

template<class T>
struct f {
  f(std::span<const T>);
  // operator ReturnType();
};
template<class T> f(std::span<T>)->f<T>;

It’s debatable whether the interface describes the intent here.

Upvotes: 4

ALX23z
ALX23z

Reputation: 4713

It is unable to make the type conversion because it fails to deduce the type as f is a template.

If you want to write template function f that accepts various input types - you'd better write it as

 template<typename Viewer>
 void f(const Viewer& a){...}

If you want to work with span then you can implement it in two steps:

 template<typename T>
 void f_impl(std::span<const T> a){...}

 template<typename Viewer>
 void f(const Viewer& a)
 {
      f_impl(std::span<const typename Viewer::value_type>(a.data(),a.size()));
  }

P.S. with span you should normally take copy of it instead of "const reference". This is more efficient.

Upvotes: 0

Related Questions