0

I frequently have to implement "container-like" classes in C++ and I find that I often forget to implement one part of the interface and have problems later. To fix this I want to create an abstract Container class that I can make my container classes inherit from that will fail to compile if any of the required interface functions are not implemented.

The issue is container classes typically define a size_type and I want the size() function to return the corresponding size_type of whatever the underlying container is.

Here is a simplified example of what I am talking about:

#include <vector>

// Abstract class
template <typename T>
class Container
{
public:
    virtual ~Container() = 0;

    //********************************************************************************
    // How do I tell it to return whatever "size_type" is for the child class?
    //********************************************************************************
    virtual typename ChildClass::size_type size() const = 0;
};

template <typename T>
Container<T>::~Container() = default;

// Concrete
template <typename T>
class MyVector final : public Container<T>
{
public:
    // size_type of the child class defined here
    using size_type = typename std::vector<T>::size_type;

    MyVector()
        : m_data()
    {}

    size_type size() const override
    {
        return m_data.size();
    }

private:
    std::vector<T> m_data;
};

Is there any way to reference the size_type of whatever the child class is in the abstract function definition of size()?


EDIT

After applying the CRTP (recommended below) I feel like I am much closer, but it is still failing to compile the second I try to instantiate an object of MyVector<double>:

// Abstract class
template <typename T>
class Container {
  public:
    virtual ~Container() = default;

    // Still can't seem to resolve the type here
    virtual typename T::size_type size() const = 0;
};

// Concrete
template <typename T>
class MyVector final : private Container<MyVector<T>> {
  public:
    using size_type = typename std::vector<T>::size_type;
    using value_type = T;

    size_type size() const
    {
        return m_data.size()
    }

private:
    std::vector<T> m_data;
};

int main()
{
    // Fails to compile
    MyVector<double> v;
}

I get the compiler error:

error C2039: 'size_type': is not a member of 'MyVector'

tjwrona1992
  • 8,614
  • 8
  • 35
  • 98
  • That won't work because `T` is the type of what is stored in the container, not the container itself. In this case I would need `ChildClass` to resolve to `std::vector::size_type` – tjwrona1992 Nov 03 '20 at 17:55
  • @cigien It looks like `Container` is templated over the thing it's containing, not using CRTP. That said, using CRTP and having something like `class MyVector final : public Container>` could allow this. – Nathan Pierson Nov 03 '20 at 17:55
  • Aah, I see, the code *looked* like it was already using CRTP, and I jumped to conclusions. Sorry, removed the comment. – cigien Nov 03 '20 at 17:57
  • I've deleted my answer which used CRTP, since that couldn't work. See: https://stackoverflow.com/questions/6006614/c-static-polymorphism-crtp-and-using-typedefs-from-derived-classes. – AVH Nov 03 '20 at 18:22
  • That link had what I was looking for! It's a bit ugly but it works. – tjwrona1992 Nov 03 '20 at 18:37

2 Answers2

0

From the answer to a related question I figured out something that works. It's a bit ugly since it requires me to define a ContainerTraits struct for each container I want to implement, but it does get the job done.

template <typename T>
struct ContainerTraits;

// Abstract class
template <typename T>
class Container {
public:
    using size_type = typename ContainerTraits<T>::size_type;

    virtual ~Container() = default;

    virtual typename size_type size() const = 0;
};

// Concrete
template <typename T>
class MyVector final : private Container<MyVector<T>> {
public:
    using size_type = typename ContainerTraits<MyVector>::size_type;

    size_type size() const override { return m_data.size(); }

private:
    std::vector<T> m_data;
};

template <typename T>
struct ContainerTraits<MyVector<T>>
{
    using size_type = typename std::vector<T>::size_type;
};
tjwrona1992
  • 8,614
  • 8
  • 35
  • 98
  • there's a `typename` too much in `Container::size()`... in C++20 all `typename`s can even be left out altogether. – JHBonarius Nov 03 '20 at 20:13
  • I also just learned today that C++20 will have a concept for "Container" types which makes this entire exercise obsolete. XD – tjwrona1992 Nov 03 '20 at 23:25
0

I was thinking... how do you pass typenames to a class? As template parameter!

So you could do something like this:

#include <vector>

// interface
template <
    /*typename container_type, // not required in this example */
    typename size_type
>
class Container {
public:
    virtual ~Container() = default;

    virtual size_type size() const = 0;
};

template <
    typename value_type,
    typename size_type = typename std::vector<value_type>::size_type
>
class MyVector final :
    public Container<
        /*MyVector<T>,*/
        size_type
    >
{
public:
    size_type size() const
    {
        return m_data.size();
    }

private:
    std::vector<value_type> m_data;
};

int main()
{
    MyVector<double> v;
}
JHBonarius
  • 10,824
  • 3
  • 22
  • 41