Search code examples
c++templatesvectorreturn-typecrtp

Calling function in derived class from base class of crtp with generic return type


I am creating a library which allows the user to store a pointer and later retrieve the pointer.
I have a class which stores the pointer of a generic type.

template <typename generic_type>
class A 
{
public:
    A() {}
    A(generic_type *value) : data(value) {}
    generic_type *getData()
    {
        return data;
    }

private:
    generic_type *data;
};

I want to also store the instances of these class template in a vector, so i inherited from a base class.

class B 
{
};

template <typename generic_type>
class A : public B
{
...
};

class test_class
{
};

std::vector<B *> list;
// -------- example -------
// test_class *test = new test_class();
// list.push_back(new A<test_class>(test));
// list.push_back(new A<test_class>(test));
// list.push_back(new A<test_class>(test));

When retrieving from the list i will get a pointer to base class.
To call the functions in derived class A, it will have to be cast.

// example
B *bp = list.at(0); 
A<test_class> *ap = static_cast<A<test_class> *>(bp);

I do not want the user to manually have to cast the base pointer themselves, but be able to call the getData() function in A which will return the actual data based on the generic_type.

// preferred API usage
B *bp = list.at(0); // they will get B pointer in another way but shown here for simplicity
test_class *t = bp->getData();

Looking around for calling child function from parent class, i came upon CRTP, which allowed me to call the function in A;but now i am unable to store it in a vector as B will be templated. So i derived B from another class referencing code from this post.
This allowed me to store it in a vector but i cant seem to find a way to call the function with generic return type and return it.

class C
{
public:
  virtual void func() = 0;
};
template <typename derived>
class B : public C
{
public :
  void func() // cant change to templated return type as virtual function cant be templated
  {
    derived *p = static_cast<derived *>(this);
    p->getData(); // how to return the value from this ?
  }
};

template <typename generic_type>
class A : public B<A<generic_type>>
{
...
};

std::vector<C *> list;
// -------- example -------
// this allows me to do  
C *cp = list.at(0);
cp->func(); // will call getData() indirectly
            // but am unable to get the returned data

I am not familiar with newer features or design patterns as i have not used c++ for a few years. Is it possible to get the returned data? or is there a different method or pattern i could use to achieve the desired API usage scenario?


Solution

  • You will never get around the fact that the user has to specify which type they expect to receive. The compiler needs to know at compile-time what the return type of getData will be, so you have to decide at compile-time which concrete A instantiation you are operating on.

    However, there is a way to make this "specifying the type" more convenient than a manual static cast: Provide an implicit conversion in B to any pointer.

    class B 
    {
    public:
        virtual ~B() = default;
    
        template<class T>
        operator T*();
    };
    
    template <typename generic_type>
    class A : public B
    {
      // ...
    };
    
    template<class T>
    B::operator T*()
    {
        A<T>* a = dynamic_cast<A<T>*>(this);
        if (!a)
          return nullptr;
        return a->getData();
    }
    
    // Usage:
    
    std::vector<std::unique_ptr<B>> list;
    list.emplace_back(new A<test_class>);
    list.emplace_back(new A<int>);
    list.emplace_back((A<double>*)nullptr);
    
    test_class* out1 = *list[0];   // Returns the correct ptr
    test_class* out2 = *list[1];   // Returns nullptr (incorrect type)
    double* out3 = *list[2];       // Returns nullptr (data value is null)
    

    https://godbolt.org/z/NT1AF8

    This conversion operator assumes that, when we try to convert some object x from a B to some T*, that the dynamic type of x is A<T*>. It attempts the down-cast and returns getData() if it succeeds, or nullptr otherwise.

    This works just fine in principle, you should however be aware that implicit conversions are better used sparingly or not at all - they can lead to very surprising behavior. You might also want to (SFINAE-)guard against someone creating an A<A<something>> or A<B>, because that sounds like a headache.

    Getting it to work with cv specifiers (i.e. const/volatile) will require some more work (including specifying whether A<const int> should be a thing or not).


    You could also take the middle route between manual static_cast and the implicit conversions above by writing a free accessor function like

    template<class T>
    T* getData(B& b)
    {
      // See operator T* implementation above.
    }
    

    which would be used like double* y = getData<double>(x);. I would prefer this solution for a library interface.