vendredi 22 avril 2022

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?

Aucun commentaire:

Enregistrer un commentaire