When you need std::remove_const

I ran across an interesting case where I needed to use std::remove_const to ensure a template parameter wasn’t const by default. I had something like this:

const Matrix<4> m4 = Identity;
Vector<3> v3 = project(m4[3]);

With the library I was using, TooN 2.0.0 beta8, this resulted in a compile error. The error stated that when the vector returned from project() was being created, the compiler couldn’t invoke assign to a read only location. A simplified declaration of project() looks as follows:

template <int Size, typename Precision, typename Base>
Vector<Size-1,Precision> project(const Vector<Size, Precision, Base>& v);

What the project() function returns is not important; what is important is the use of the template parameter Precision in both the argument type and the return type. When I invoke operator[] on a const Matrix object, the returned vector’s precision inherits the const.

template <int Rows, int Cols, class Precision>
class Matrix {
...
Vector<Cols, const Precision, Slice> Matrix::operator[](int row) const;
};

When this vector is passed to the project() function above, the function tries to create and return a Vector<3, const double> which can only be initialized with a constexpr. Assigning to it via operator= or a constructor will not work to copy the data from the other vector.

There are several ways one can fix this, all of them involving removing the const qualifier from the Precision type on the returned vector.

It may seem like a simple solution would work:

template <int Size, typename Precision, typename Base, typename P2>
Vector<Size-1, P2> project(const Vector<Size, Precision, Base>& v);

Unfortunately, this requires the function be invoked as project<Size, Precision, Base, P2>(v); which is undesirable.

Solution #1

The first solution is simple, but tedious if you have several functions with similar signatures:

template <int Size, typename Precision, typename Base>
Vector<Size-1, typename std::remove_const<Precision>::type > 
project(const Vector<Size, Precision, Base>& v);

std::remove_const<T>::type is a type expression equal to the original type T, but with any const qualifiers removed. In this way, const double becomes simply double.

Solution #2

Another solution is to prevent the creation of a Vector object with a const precision, the idea that a non-slice vector with const data isn’t very useful — you can’t even construct one properly. Generally the only time you would actually want a vector with const data is when that data is pointing to some already existing area in memory (a slice).

Consider the following toy code for a vector class:

#define DECLARE_TYPES(T) \
    typedef typename std::remove_const<T>::type PlainType; \
    typedef const T ConstPlainType; \
    typedef typename std::remove_const<T>::type& ReferenceType; \
    typedef const T& ConstReferenceType

struct StackBase {
template <int Size, typename P>
class Layout { 
public:
    DECLARE_TYPES(P);
    PlainType data[Size];
};
};

struct SliceBase {
template <int Size, typename P>
class Layout {
public:
    P* data;
};
};

template <int Size, typename P = double, typename B = StackBase >
class Vector : public B::template Layout<Size, P> {
public:
    DECLARE_TYPES(P);
    using B::template Layout<Size, P>::data;

    ConstReferenceType operator[](int i) const { return data[i]; }
    ReferenceType operator[](int i) { return data[i]; }
};

When the Vector object is backed by owned storage (StackBase), the data is declared explicitly to be of a non-const type. Similarly, the return types of operator[] are defined to be the const and non-const reference types. The SliceBase, which handles references to un-owned data, retains the original templated type to allow pointing to constant data.

While this method prevents the creation of Vector objects with const owned data, it also means you don’t have to augment functions that interface with these objects to correct the return type.

Posted in Software | Tagged , | Leave a comment

Leave a Reply

Your email address will not be published.