3

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?

bitDaft
  • 177
  • 10
  • 1
    The retrun type of `getData` has to be known at compile time. When you are just given an `*B` then there is no way to tell the correct return type. Would it be an option for the user to list all possible types? then we could look into unions. –  Sep 02 '19 at 07:14
  • the user can pass in any type of pointer. therefore the library will have no knowledge of what the type will be. the user can cast it to the correct type since they know what type they passed. since i read that CRTP was compile time, i wanted to know if it was possible to cast it in the library's code and just provide the data pointer – bitDaft Sep 02 '19 at 07:18
  • i am not insisting on this very structure of code. if redesign is required i am open to it. i would like to know how to do it with unions as mentioned. There is no fixed number of types. user can pass in pointer to anything, even custom class objects. The only requirement is the user can send in pointers which will be stored in a vector and later will need to get the stored pointer back. – bitDaft Sep 02 '19 at 07:26
  • The requirements "an arbitrary set of user-supplied types can be passed in and retrieved" is mutually exclusive with "the user does not need to specify which type they think should be returned". However, there is some way of making the "specify the type" part more convenient (albeit dangerous)... – Max Langhof Sep 02 '19 at 07:54

1 Answers1

2

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.

Max Langhof
  • 23,383
  • 5
  • 39
  • 72
  • 1
    Are you sure it works with const? The forth example returns `nullptr` because it fails to cast to `A*`, not because the data is `nullptr` – Caleth Sep 02 '19 at 10:04
  • I tried both the solutions. i also put the free function as a member of B. it worked and i was able to get the data. i haven't tried with `const` as mentioned by @Caleth. i did think about having the templated free function for conversion but wanted to see if any other way was possible without specifying the type. what is the normal preferance for library interface, free standing or member function? where can i learn more about the conversion operator as i have never used it. thank you – bitDaft Sep 02 '19 at 10:34
  • what kind of surprising behavior. are there any issues in using implicit conversions? – bitDaft Sep 02 '19 at 10:44
  • 1
    @bitDaft You can easily get [ambiguities](https://stackoverflow.com/questions/32068106/implicit-conversions-from-and-to-class-types), [unwanted conversions](https://stackoverflow.com/questions/2346083/why-implicit-conversion-is-harmful-in-c), [unexpected temporaries](https://devblogs.microsoft.com/oldnewthing/20060524-12/?p=31083) and more. It's not impossible to write code where implicit conversions fit in fine, but it's very easy to run into subtle or inconvenient issues. I recommend reading up on the matter yourself though. – Max Langhof Sep 02 '19 at 10:55
  • 1
    @bitDaft Oh, and it also won't work with [function templates](https://stackoverflow.com/questions/13883067/implicit-conversion-to-stdstring), which may or may not be of relevance. – Max Langhof Sep 02 '19 at 11:03