I think the problem is fairly common, so there should be a known solution. I came up with one, but I'm not really satisfied, so I'm asking here, hoping someone can help.
Say I have a function, whose signature is
template<typename T>
void foo(const MyArray<const T>& x);
The const in the template parameter is to prevent me from changin the array content, since (for reasons beyond this question), the accessors ([]
and ()
) of MyArray<T>
are always marked const, and return references to T
(hence, the const ensure safety, since MyArray<T>::operator[]
returns T&
, while MyArray<const T>::operator[]
returns const T&
).
Great. However, templates with different template arguments are non related, so I can't bind a reference to MyClass<T>
to a reference of MyClass<const T>
, meaning I can't do this
MyArray<double> ar(/*blah*/);
foo(ar);
Notice that, without a reference, the code above would work provided that there is a copy constructor that lets me create MyArray<const T>
from MyArray<T>
. However, I don't want to remove the reference, since the array construction would happen a lot of times, and, despite relatively cheap, its cost would add up.
So the question: how can I call foo
with an MyArray<T>
?
My only solution so far is the following:
MyArray<T> ar(/*blah*/);
foo(reinterpret_cast<MyArray<const T>>(ar));
(actually in my code I hid the reinterpret cast in an inlined function with more verbose name, but the end game is the same). The class MyArray
does not have a specialization for const T
that makes it not reinterpretable, so the cast should be 'safe'. But this is not really a nice solution to read. An alternative, would be to duplicate foo
's signature, to have a version taking MyArray<T>
, which implementation does the cast and calls the const version. The problem with this is code duplication (and I have quite a few functions foo
that need to be duplicated).
Perhaps some extra template magic on the signature of foo
? The goal is to pass both MyArray<T>
and MyArray<const T>
, while still retaining const-correctness (i.e., make the compiler bark if I accidentally change the input in the function body).
Edit 1: The class MyArray
(whose implementation is not under my control), has const accessors, since it stores pointers. So calling v[3]
will modify the values in the array, but not the members stored in the class (namely a pointer and some smart-pointer-like metadata). In other words, the object is de facto not modified by accessors, though the array is. It's a semantic distinction. Not sure why they went this direction (I have an idea, but too long to explain).
Edit 2: I accepted one of the two answers (though they were somewhat similar). I am not sure (for reasons long to explain) that the wrapper class is doable in my case (maybe, I have to think about it). I am also puzzled by the fact that, while
template<typename T>
void foo(const MyArray<const T>& x);
MyArray<int> a;
foo(a);
does not compile, the following does
void foo(const MyArray<const int>& x);
MyArray<int> a;
foo(a);
Note: MyArray
does offer a templated "copy constructor" with signature
template<typename S>
MyArray(const MyArray<S>&);
so it can create MyArray<const T>
from MyArray<T>
. I am puzzled why it works when T
is explicit, while it doesn't if T
is a template parameter.
I would stay with
template<typename T>
void foo(const MyArray<T>&);
and make sure to instantiate it with const T
(in unitTest for example).
Else you can create a view as std::span
.
Something like (Depending of other methods provided by MyArray
, you probably can do a better const view. I currently only used operator[]
):
template <typename T>
struct MyArrayConstView
{
MyArrayConstView(MyArray<T>& array) : mArray(std::ref(array)) {}
MyArrayConstView(MyArray<const T>& array) : mArray(std::ref(array)) {}
const T& operator[](std::size_t i) {
return std::visit([i](const auto& a) -> const T& { return a[i]; }), mArray);
}
private:
std::variant<std::reference_wrapper<MyArray<T>>,
std::reference_wrapper<MyArray<const T>>> mArray;
};
and then
template <typename T>
void foo(const MyArrayConstView<T>&);
but you need to call it explicitly, (as deduction won't happen as MyArray<T>
is not a MyArrayConstView
)
MyArray<double> ar(/*blah*/);
foo(MyArrayConstView{ar});
foo<double>(ar);