'C++ Reversing the operation of type erasure

The example code which I write as part of this question probably will seem contrived and without much useful purpose, but that is because it is a minimal example rather than a convoluted code which doesn't convey the question succinctly.

I want to reverse the operation of type erasure. I assume there isn't a design pattern for doing this - if there is I either don't know of it or haven't realized how it can be used for this purpose.

Consider the following type-erasure.

class Base
{

}

template<typename T>
class Typeless : Base
{
    T data;
}

The purpose of this is to store base class pointers in a container.

std::container<Base&> container;

The type has been erased.

However, elsewhere in my code I want to provide static type enforcement.

template<typename U>
class Reference
{
    U &external_ref_to_object;

    Reference(U &ref)
        : external_ref_to_object(ref)
    {}
}

Base *p_tmp = new Typeless<int>;
container.push_back(p_tmp);
Reference<int> r(p_tmp);

The point here is that although container can contain objects of any type due to the type-erasure pattern which has been utilized, the Reference class should enforce the correct type to be used.

If this example is confusing, more context might be helpful. I am writing something which is not too dissimilar from a database application. container is basically a collection of all the data to be managed, regardless of what type the data is. (It avoids having a container<T> for each unique T as shown below.)

// avoid this:
std::container<int> all_integer;
std::container<float> all_floating_point;
std::container<std::string> all_text;
std::container<void*> anything_else;

Hence the type erasure.

The purpose of "getting the type back again" is to enforce all database columns to contain objects of the same type.

DBColumn<int> my_column_contains_int_type_data;
my_column_contains_int_type_data.insert(42);

PS: Try not to be distracted by the fact this code clearly does not compile. It is intended to be a sketch to demonstrate the question.

As a final comment, it occurred to me that this is vaguely similar to a factory pattern. (Although not quite the same.) We already have the object. We don't need to clone it, only store a reference to it. We don't need to load anything from user input, network, disk or other external source. So it isn't a creational factory and it isn't a clone factory either.

Factories can use some form of centrally managed and generated unique id's to indicate what type an object is. For example, one might save and load from disk data with an identifier in the header which indicates what type of object is represented by the data on disk and therefore how an object should be created dynamically.

In my case, it would be possible to have such an identifier for each derived class (each unique T in Typeless<T>) however this doesn't seem like a particularly good solution, as I fear I may write the following block of code.

class DBColumn<T>
if(identifier == "INT")
{
    if(typeof(T) is int)
    {
        // good
    }
    else
    {
        throw "BAD"; // bad
    }
}
else if(...) // repeat for each of int, float, double, std::string, etc

Hopefully the question is fairly clear?



Solution 1:[1]

You don't need a separate identifier, you can simply if (typeof all the possible types.

if(typeof(T) is Typeless<int>)
    doIntColumnThing(static_cast<Typeless<int>>(T));
else if(typeof(T) is Typeless<float>)
    doFloatColumnThing(static_cast<Typeless<float>>(T));
else if(typeof(T) is Typeless<double>)
    doDoubleColumnThing(static_cast<Typeless<double>>(T));
else if(typeof(T) is Typeless<std::string>)
    doDoubleColumnThing(static_cast<Typeless<std::string>>(T));

(or you can generate fancy template metamagic that makes it pretty, but fundamentally does the same thing)

The better design is usually to instead keep them separate from the beginning:

DBColumn<Typeless<int>> my_column_1;
DBColumn<Typeless<std::string>> my_column_2;
std::vector<Base> all_cells;

void add_column_2(Typeless<std::string>& cell) {
    my_column_2.add(cell);
    all_cells.add(cell);
}

The best answer is usually to put all your operations in the interface, but I assume you've already made a design where that's impossible. I would highly recommend redesigning it so that you can do this way, but I assume you won't.

class Base
{
    virtual void doColumnThing();
}

template<typename T>
class Typeless : Base
{
    T data;
    void doColumThing() { 
        doColumnThing(data);
    }
}

Solution 2:[2]

There is a pattern for this, the command pattern. One stated purpose is for occasions when "undo" operations might be needed. The idea is to encapsulate all the information needed for an action to be performed and undone so it doesn't rely on a particular point in the program flow.

I know this is a Java page but it explains the concept pretty clearly: https://java-design-patterns.com/patterns/command/

To add, the GoF recommendation for undo is to store any original state information that may change as a result of the action in the command object. In this way the command object knows how to perform the action in both directions and has all the info necessary to return the receiver to its original state. So it's nicely decoupled from the 'Invoker' class.

If the info is complex, you could have another object for that state info to keep things from getting clogged up in the command itself.

In your case that might well include the type info, or some other method to restore the correct original type if that's possible.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Mooing Duck
Solution 2