Reputation: 735
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
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
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