C++ template with 'const'

Sanparith Marukatat picture Sanparith Marukatat · Sep 12, 2014 · Viewed 7.2k times · Source

Consider the following template class:

template <typename T> class Function {
public:
    virtual float eval( const T &x, const T &y ) = 0;
};

Since the 'eval' function should not modify the value of the two inputs 'x' and 'y', I put them as 'const'. Then I create the following class derived from Function

class Foo1 : public Function <float*> {
public:
    Foo1() : Function <float*> () {}
    virtual float eval( const float* &x, const float* &y ) { ... }
};

When I compile with g++, I get the following warning:

hidden overloaded virtual function 'Function<float *>::eval' declared here: type mismatch at 1st parameter
      ('float *const &' vs 'const float *&')
        virtual float eval( const T &x, const T &y ) = 0;

And I cannot instantiate the class Foo1. The compiler says that the function 'eval' is not implemented. To make the compiler happy, the derived class must be as follows:

class Foo2 : public Function <float*> {
public:
    Foo2() : Function <float*> () {}
    virtual float eval( float* const &x, float* const &y ) { ... }
};

Foo2::eval function uses two parameters of type 'float * const' instead of 'const float *'. In other words, Foo2::eval can modify the content of the arrays 'x' and 'y'. This is not what I want.

I have tried to change the template class 'Function' as follows:

    virtual float eval( T const &x, T const &y ) = 0;

But the class Foo1 still does not work, the class Foo2 works as in previous case.

  1. So it seems that either 'const T &x' or 'T const &x' in the template class implies 'float* const &x' in the derived class. Is this correct?
  2. If I want 'const float* &x' (or 'const float* x') in the derived class, what should be my template class Function?

Thank you.

Answer

The behaviour you observe is 100% correct as far as the standard is concerned. It's the classic example of "constant pointer" versus "pointer to constant."

Your primary template declares it takes a "reference to T through which it cannot modify the T object being referred to" (the syntax is const T &, equivalent to T const &).

Then, you instantiate the template with a type float*, i.e. "pointer to float." From this follows that the function parameter type after template parameter substitution is indeed "reference to float * through which it cannot modify the float * begin referred to." There's no way to smuggle "cannot modify the float to which the float * being referred to points" into there directly.

I see two options. One, if this kind of use is the only use of T in Function, simply use const float * as the template argument, since that's the T you really want:

class Foo1 : public Function <const float*> {
public:
    Foo1() : Function <const float*> () {}
    virtual float eval( const float* const &x, const float* const &y ) { ... }
    // notice two `const` keywords above: one for the pointed object, one for the reference
};

If that is not an option for you (i.e. if you need float * somewhere inside Function and const float* elsewhere), you'll have to use some type traits and modify the parameters of eval. Something like this:

template <class T>
struct unmodifiable {
  typedef const T &type;
};

template <class T>
struct unmodifiable<T*> {
  typedef const T* const &type; // if you want to keep the reference
  // or just:
  // typedef const T *type;  // if you don't want to bother with the reference for pointer types
};

template <typename T> class Function {
public:
    virtual float eval( typename unmodifiable<T>::type x, typename unmodifiable<T>::type y ) = 0;
};

class Foo1 : public Function <float*> {
public:
    Foo1() : Function <float*> () {}
    virtual float eval( unmodifiable<float*>::type x, unmodifiable<float*>::type y ) { ... }
    // or just spell it out exactly, based on the variant of `unmodifiable` you've chosen, e.g.:
    // virtual float eval (const float *x, const float *y) { ... }
};