1

I am trying to understand the behavior of "Type Erasure" by using std::make_shared. The basic idea is to use a class Object to wrap some different classes, such as class Foo and class Bar.

I write the following code, and it does work.

// TypeErasure.cpp

#include <iostream>
#include <memory>
#include <string>
#include <vector>

class Base
{
public:
    virtual ~Base() {}
    virtual std::string getName() const = 0;
};

template< typename T >
struct Derived : Base
{
public:
    explicit Derived(const T&& t) : objD(t) {}
    std::string getName() const override
    {
        return objD.getName();
    }
private:
    T objD;
};

class Object
{

public:
    template <typename T>
    explicit Object(T&& t)
        : objPtr(std::make_shared<Derived<T>>(std::forward<T>(t))) {}

    std::string getName() const
    {
        return objPtr->getName();
    }

    std::shared_ptr<const Base> objPtr;
};


void printName(std::vector<Object> vec)
{
    for (auto v: vec) std::cout << v.getName() << std::endl;
}

class Bar
{
public:
    std::string getName() const
    {
        return "Bar";
    }
};

class Foo
{
public:
    std::string getName() const
    {
        return "Foo";
    }
};

int main()
{
    std::vector<Object> vec{Object(Foo()), Object(Bar())};

    printName(vec);

}

but when I change "struct Derived : Base" into "class Derived : Base", it shows the following error.

error: no matching function for call to 'std::shared_ptr::shared_ptr(std::shared_ptr)'|

The code is as following.

// TypeErasure.cpp

#include <iostream>
#include <memory>
#include <string>
#include <vector>

class Base
{
public:
    virtual ~Base() {}
    virtual std::string getName() const = 0;
};

template< typename T >
class Derived : Base
{
public:
    explicit Derived(const T&& t) : objD(t) {}
    std::string getName() const override
    {
        return objD.getName();
    }
private:
    T objD;
};

class Object
{

public:
    template <typename T>
    explicit Object(T&& t)
        : objPtr(std::make_shared<Derived<T>>(std::forward<T>(t))) {}

    std::string getName() const
    {
        return objPtr->getName();
    }

    std::shared_ptr<const Base> objPtr;
};


void printName(std::vector<Object> vec)
{
    for (auto v: vec) std::cout << v.getName() << std::endl;
}

class Bar
{
public:
    std::string getName() const
    {
        return "Bar";
    }
};

class Foo
{
public:
    std::string getName() const
    {
        return "Foo";
    }
};

int main()
{
    std::vector<Object> vec{Object(Foo()), Object(Bar())};

    printName(vec);

}

What is the root cause of this error? Is it about the difference between class and struct? Is it because class is a reference and struct is a value?

  • There are [very few differences](https://stackoverflow.com/questions/92859/what-are-the-differences-between-struct-and-class-in-c) between `class` and `struct` in C++. In this case the relevant difference is that `struct Derived : Base` means `Derived` is _publically_ inheriting from `Base`, but `class Derived : Base` means `Derived` is _privately_ inheriting from `Base`. In the latter case because the "is-a" relationship is private, you can't create a `shared_ptr` out of a `Derived`. – Nathan Pierson Mar 31 '22 at 14:18

1 Answers1

7

The only real difference between a class and a struct in C++ is that, for a struct, the default member access and inheritance is public, whereas, for a class, the default is private.

So, to make your code work for the class Derived template, just make its inheritance of Base public:

template< typename T >
class Derived : public Base { // public inheritance
public:
//...

Such public inheritance gives the Derived class access to the Base class constructors.


Alternatively, to make your struct template case fail – most likely with the exact same error message(s) – you can make its inheritance of Base private:

template< typename T >
struct Derived : private Base { // private inheritance - fails to compile!
public:
//...
Adrian Mole
  • 49,934
  • 160
  • 51
  • 83