0

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

KentH
  • 1,152
  • 1
  • 12
  • 22
  • There would be no way of doing this in C++. You are specifying the type of an area of memory to be a particular type at the get operation with no way of knowing from within the class what is actually stored in the type. How would auto "know" that the type is being returned? A template "list" of types might be what those libraries use however, this would still involve selecting from the "list" by number which of the types you wanted returning i.e. get<5>() of list would get you the char and return that. Not much of an improvement really. – ceorron Jun 12 '15 at 22:22
  • 1
    This code violates the strict aliasing rule (`char` may not be aliased as `T`); to fix this change `alignas(8) char data[8];` to `aligned_storage<8,8> data;` – M.M Jun 13 '15 at 00:37

3 Answers3

1

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.

Barry
  • 247,587
  • 26
  • 487
  • 819
0

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.

Remy Lebeau
  • 454,445
  • 28
  • 366
  • 620
  • It is derived from an example I found in [this `std::type_index` documentation](http://en.cppreference.com/w/cpp/types/type_index). It is not perfect, I admit, but it should be useful. About as close as `KentH` is going to be able to get considering `d_union`'s data type is not known until runtime. – Remy Lebeau Jun 12 '15 at 23:10
0

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.

Peter
  • 32,539
  • 3
  • 27
  • 63