Is it possible to automatically extract type from discriminated union?

412 views Asked by At

Is it possible to extract the type of a discriminated union to initialize an "auto" variable? It's easy enough is you pass the type to a template, but I'd like something "auto". A solution using a visitor function or using a bounded list of types (such as an mpl::vector) would be great.

An example is shown below:

#include <iostream>
#include <typeindex>
#include <cassert>

struct d_union {
    template <typename T>
    d_union(T t) {
        *reinterpret_cast<T*>(data) = t;
        _type_id = &typeid(T);
    }

    template <typename T>
    const T* get_pointer() const {
        if (_type_id == &typeid(T))
            return reinterpret_cast<const T*>(data);
        else
            return nullptr;
    }

    template <typename T>
    const T get() const {
        assert (_type_id == &typeid(T));
        return *get_pointer<T>();
    }

    alignas(8) char data[8];
    const std::type_info *_type_id;
};


std::ostream& operator<<(std::ostream&os, const d_union &u) {
    if (auto ip = u.get_pointer<int>())
        os << *ip;
    if (auto fp = u.get_pointer<float>())
        os << *fp;
    return os;
}

int main() {

    d_union _i = d_union(42);
    d_union _f = d_union(3.14f);

    std::cout << "d_union(42) = "   << _i << std::endl;
    std::cout << "d_union(3.14) = " << _f << std::endl;

    int _get_i = _i.get<int>();
    std::cout << "d_union(42).get<int>() = " << _get_i << std::endl;

    // auto _get_auto = _i.get();
    // std::cout << "d_union(42).get()" << _get_auto << std::endl;

}

Any possible solutions would be appreciated!

Thanks

3

There are 3 answers

1
Remy Lebeau On

Typically, when you override the global stream operators for a custom class type, you should implement them in terms of calls to member methods and let the class decide how to stream itself, eg:

struct d_union {
    ...
    void outputTo(std::ostream &os) const {
        if (auto ip = get_pointer<int>())
            os << *ip;
        else if (auto fp = get_pointer<float>())
            os << *fp;
        ...
    }
    ...
};

std::ostream& operator<<(std::ostream &os, const d_union &u) {
    u.outputTo(os);
    return os;
}

That being said, if you want something a little more "auto" when operator<< is invoked, maybe you could try something like this (not perfect, but close to what you might be looking for considering d_union does not know what it is holding until runtime, and if you added operator= then it could even change type dynamically):

typedef void (*pOutputProc)(const d_union&, std::ostream&);

template <typename T>
void outputProc(const d_union &u, std::ostream &os)
{
    os << *(u.get_pointer<T>());
}

std::unordered_map<std::type_index, pOutputProc> outputProcs;

template <typename T>
void registerOutputProc()
{
    outputProcs[std::type_index(typeid(T))] = &outputProc<T>;
}

void registerOutputProcs()
{
    registerOutputProc<int>();
    registerOutputProc<float>();
    ...
}
#pragma startup registerOutputProcs

struct d_union {
    ...
    void outputTo(std::ostream &os) const {
        pOutputProc proc = outputProcs[std::type_index(*_type_id)];
        if (proc) proc(*this, os);
    }
    ...
};

std::ostream& operator<<(std::ostream &os, const d_union &u) {
    u.outputTo(os);
    return os;
}

Then you would just have to populate registerOutputProcs() with the various data types that you want d_union to support.

0
Peter On

In general, it is not possible, since C++ is not a dynamically typed language.

In specific cases, it would be necessary for (in your case) the operator<<() to examine the type_info and do things based on what type is actually there. The limitation of that is that it is necessary to hard-code knowledge of what types may be stored in your struct - if you want to add support for different types, it will be necessary to add code and rebuild your program.

auto relies on the compiler knowing the type at compile time. type_info gets values at run time.

0
Barry On

You're looking for the Boost.TypeErasure library. That will let you stream any stream able in a natural way. From the tutorial:

any<
    mpl::vector<
        copy_constructible<>,
        typeid_<>,
        incrementable<>,
        ostreamable<>
    >
> x(10);
++x;
std::cout << x << std::endl; // prints 11

x here can be of any type that satisfies the given concepts.

If that isn't quite what you want, then Boost also has a discriminated union library called Variant which has a visitor interface.