Search code examples
c++templatesconstantstemplate-argument-deduction

Deducing the template parameter of a templated class argument: const issue


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.


Solution

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