Trevir
Trevir

Reputation: 1313

Container Object of Templated Class

Below is the (very) stripped down code of a implementation.

The library code is as follows:

#pragma once
#include <iostream>
#include <map>
#include <string>

// Necessary as interface and due to QObject Macro.
class Base
{
  public:
    Base( const std::string& name ) : name_( name )
    {
    }

    virtual ~Base(){}

    const std::string& name()
    {
        return name_;
    }

  private:
    std::string name_;
};

template < typename Derived, typename ObjectType >
class TemplatedBase : public Base
{
  public:
    TemplatedBase( const std::string& name ) : Base( name )
    {
    }

    ObjectType object()
    {
        return object_;
    }

    ObjectType object_;
};

class DerivedA : public TemplatedBase< DerivedA, int >
{
  public:
    DerivedA( const std::string& name ) : TemplatedBase< DerivedA, int >( name )
    {
    }
};

class DerivedB : public TemplatedBase< DerivedB, float >
{
  public:
    DerivedB( const std::string& name ) : TemplatedBase< DerivedB, float >( name )
    {
    }
};

class Container
{
  public:
    template < typename T >
    void addToMap( T& map_object )
    {
        const std::string name = map_object.name();
        // ASSERT( map_.find( name ) == map_.end() );
        map_.emplace( std::make_pair( name, &map_object ) );
    }

    template < typename T >
    auto getObject( std::string name ) -> decltype( ( ( T* )nullptr )->object() )
    {
        auto search = map_.find( name );
        // How can this dynamic_cast be avoided? 
        T* ptr      = dynamic_cast< T* >( search->second );
        // ASSERT( ptr == nullptr );
        return ptr->object();
    }

    std::map< std::string, Base* > map_;
};

With an example usage of:

int main( int argc, char* argv[] )
{
    Container container;

    DerivedA a( "Name_A" );
    DerivedB b( "Name_B" );

    container.addToMap( a );
    container.addToMap( b );

    // How can I avoid to specify the type in the map as template?
    auto object_a = container.getObject< DerivedA >( "Name_A" );
    auto object_b = container.getObject< DerivedB >( "Name_B" );
}

Some explanation for the code:

While the code itself is working fine, I am looking for a different design that makes the dynamic_cast and the type specification during readout unnecessary.

I have tried several types of type erasure (boost::type_erasure, boost::variant) and different container types. However I always ran into the problem of different return types of the object() function.

Upvotes: 2

Views: 158

Answers (2)

xaxxon
xaxxon

Reputation: 19761

You cannot avoid putting the type name here if you want to return a specific type other than Base:

    auto object_a = container.getObject< DerivedA >( "Name_A" );

There's no way for the compiler to know what to return at compilation time since the value won't be known until runtime.

As for your reinterpret cast, you're right to not like it. That needs to be a dynamic_cast or you must build some other way to tell what type your objects are at runtime into your system. Also, you probably meant static_cast not reinterpret cast. However, static_cast is also not correct if you don't absolutely know that every lookup will specify the correct corresponding type as a template parameter that matches the actual runtime type of the stored object.

Right now, if you make a bad call, your system will result in undefined behavior. Your code is "working fine" because you're not calling getObject with an incompatible templated type, but as soon as you do, your program is likely to not be working fine.

Upvotes: 2

Konrad Rudolph
Konrad Rudolph

Reputation: 545518

Let’s consider what exactly you’re doing here:

  • You are casting
    • a pointer of a base class to a pointer of its derived child class,
    • in a non-virtual inheritance chain,
    • without casting away const-ness.
  • You are not checking the result’s correctness.

Consequently, the correct cast to use here is static_cast.

However, this will break (= result in undefined behaviour) in cases where the user accidentally calls container.getObject with a mismatching type. Consequently, you should be checking the cast result’s correctness, as explained in xaxxon’s answer.

And no, there’s no way of avoiding to specify the target type in the cast. You must specify a static type to an object in order to work with it, since the result of ptr->object() isn’t type-erased. Even boost::variant requires users to specify static types whenever they actually want to access the stored object (e.g. in the signature of a visitor).

Upvotes: 1

Related Questions