Search code examples
c++templates

template const correctness cant pass const object pointer


I have the template with this definition and call it as such.

#include <iostream>

template<class T>
class MyClass
{
public:
    void Print(const T fooIn)
    {
        std::cout << fooIn->GetString() << std::endl;
    }
};

class MyObject
{
private:
    std::string str;
public:
    MyObject(std::string str)
    {
        this->str = str;
    }
    std::string GetString()
    {
        return str;
    }
};

template class MyClass<MyObject*>;

int main()
{
    const MyObject* foo = new MyObject("test");
    MyClass<MyObject*>* bar = new MyClass<MyObject*>();
    
    bar->Print(foo);

    return 0;
}

I get the error: "Cannot initialize a parameter of type Object * with an lvalue of type const Object *". I'm using Clangd

Since T=Object* why would const T not equal const Object *? I think that instead it has resolved to Object* const And how would I make it so it has the desired functionality of being constant to the object it points to and I don't want to change the instantiation to const Object *


Solution

  • Like others (including myself in the comments) have mentioned, const T resolves to a T* const, or more concretely in your case Object* const.

    This doesn't jive well when you have a function that looks like this:

    void Print(const T fooIn)
    

    There are two approaches here, which I'm going to list in order of my recommendation:

    Approach 1, don't instantiate the template with a pointer type

    template<class T>
    class MyClass
    {
        public:
        void Print(const T& fooIn) const
        {
            std::cout << fooIn.GetString() << std::endl;
        }
    };
    

    Which you'd call like so:

    class MyObject
    {
    private:
        std::string str;
    public:
        MyObject(std::string str)
        {
            this->str = str;
        }
        std::string GetString() const
        {
            return str;
        }
    };
    
    int main()
    {
        const MyObject* foo = new MyObject("test");
        MyClass<MyObject> bar;
        bar.Print(*foo);
        delete foo;
    }
    

    Demo

    If you must, add an overload to Print that takes a pointer to const:

        void Print(const T& fooIn) const
        {
            std::cout << fooIn.GetString() << std::endl;
        }
        void Print(const T* const fooIn) const
        {
            Print(*fooIn);
        }
    

    called like so:

    int main()
    {
        const MyObject* foo = new MyObject("test");
        MyClass<MyObject> bar;
        bar.Print(*foo); // non-pointer
        bar.Print(foo); // pointer
        delete foo;
    }
    

    Approach 2, if you must, write a partial template specialization for your class:

    template<class T>
    struct MyClass;
    
    template<class T>
    struct MyClass<T*> // specialized for T*
    {
        void Print(const T* const fooIn)
        {
            std::cout << fooIn->GetString() << std::endl;
        }
    };
    
    class MyObject
    {
    private:
        std::string str;
    public:
        MyObject(std::string str)
        {
            this->str = str;
        }
        std::string GetString() const
        {
            return str;
        }
    };
    

    Called like so:

    int main()
    {
        const MyObject* foo = new MyObject("test");
        MyClass<MyObject*> bar;
        bar.Print(foo);
        delete foo;
    }
    

    Live Demo


    N.B. There's probably a tagged dispatch implementation here, but I don't want to even try