1
\$\begingroup\$

Context

For one of my projects I have to use a library which works similarly to the following code :

enum LibEntry {
    Int1, Char1, Float1,
    Int2, Char2, Float2
};

union ValueUnion {
    int i;
    float f;
    char c;
};

class Object {};

void getValue(Object*, LibEntry entry, ValueUnion* val) {
    //Real code uses the Object pointer of course, this just for the example
    switch(entry) {
        case LibEntry::Int1:
            val->i=1;
            break;
        case LibEntry::Char1:
            val->c='a';
            break;
        case LibEntry::Float1:
            val->f=1.1f;
            break;
        case LibEntry::Int2:
            val->i=2;
            break;
        case LibEntry::Char2:
            val->c='b';
            break;
        case LibEntry::Float2:
            val->f=2.2f;
            break;
    }
}

My objective is to wrap it in a class to get rid of the union type, but for that I need to map the LibEntry values to the type returned by getValue().

My solution

I wrote for that two class templates (and copied an implementation of std::type_identity from cppreference since C++20 is not available yet) which gives correct results with my tests. I also encapsulated the LibEntry enum of the library in my own MyEntry enum class (with the same enumerator names for the example)

/********** type_identity **********/
template<typename T>
struct type_identity {
    using type = T;
};

/********** TemplateList **********/
// Template declaration, version used for empty lists
template<auto...>
struct TemplateList {
    template<auto>
    static constexpr bool contains = false;
};

// Specialization for non-empty lists
template<auto head, auto... tail>
struct TemplateList<head, tail...> {
    template<auto v>
    static constexpr bool contains = (v==head) || TemplateList<tail...>::template contains<v>;
};

/********** ValueToTypeMap **********/

// Template declaration, this version should not be used
template<auto, typename...>
struct ValueToTypeMap {};

// General case : if value is in List (which should be an instance of TemplateList),
// then the member typedef type is ResultType, else discard List and ResultType
template<auto value, typename List, typename ResultType, typename... Else>
struct ValueToTypeMap<value, List, ResultType, Else...> {
    using type = typename std::conditional_t<List::template contains<value>,
                                                 type_identity<ResultType>,
                                                 ValueToTypeMap<value, Else...>
                                            >::type;
};

// Specialization for the last case to check, when no default type is provided
template<auto value, typename List, typename ResultType>
struct ValueToTypeMap<value, List, ResultType> {
    static_assert(List::template contains<value>, "Map Error: could not map given value to any type");
    using type = ResultType;
};

// Specialization for (optional) default type when value was not found
template<auto value, typename Default>
struct ValueToTypeMap<value, Default> {
    using type = Default;
};

/********** The wrapper class **********/

class Foo {
public:
    // Encapsulating the library's LibEntry
    enum class MyEntry : std::underlying_type_t<LibEntry> {
        Int1 = LibEntry::Int1,
        Char1 = LibEntry::Char1,
        Float1 = LibEntry::Float1,
        Int2 = LibEntry::Int2,
        Char2 = LibEntry::Char2,
        Float2 = LibEntry::Float2
    };

private:
    Object* obj = nullptr; // Stub member for the library's object type

    template<MyEntry entry>
    using associatedType = 
            typename ValueToTypeMap<entry, TemplateList<MyEntry::Int1  , MyEntry::Int2  >, int,
                                           TemplateList<MyEntry::Char1 , MyEntry::Char2 >, char,
                                           TemplateList<MyEntry::Float1, MyEntry::Float2>, float
                                   >::type;

public:
    template<MyEntry entry>
    associatedType<entry> getEntry() {
        using type = associatedType<entry>;

        ValueUnion result {0};
        getValue(obj, static_cast<LibEntry>(entry), &result);

        return reinterpret_cast<const type&>(result);
    }
};

/********** My test code **********/

int main()
{
    Foo bar;
    auto i1 = bar.getEntry<Foo::MyEntry::Int1>();
    std::cout << typeid(i1).name() << ' ' << i1 << std::endl; // Output : i 1
    auto i2 = bar.getEntry<Foo::MyEntry::Int2>();
    std::cout << typeid(i2).name() << ' ' << i2 << std::endl; // Output : i 2
    auto c1 = bar.getEntry<Foo::MyEntry::Char1>();
    std::cout << typeid(c1).name() << ' ' << c1 << std::endl; // Output : c a
    auto c2 = bar.getEntry<Foo::MyEntry::Char2>();
    std::cout << typeid(c2).name() << ' ' << c2 << std::endl; // Output : c b
    auto f1 = bar.getEntry<Foo::MyEntry::Float1>();
    std::cout << typeid(f1).name() << ' ' << f1 << std::endl; // Output : f 1.1
    auto f2 = bar.getEntry<Foo::MyEntry::Float2>();
    std::cout << typeid(f2).name() << ' ' << f2 << std::endl; // Output : f 2.2

    // Outputs are correct for these cases
    return 0;
}

Now I have several question about my solution :

  • Are there any case for which my class templates will not work as intended ?
  • To avoid checking for which member of ValueUnion I have to return, I instead thought of casting it to the correct type with reinterpret_cast<const type&>(result);, this seems to work with my tests, but I'm not sure whether it will always work or not. Are there any case for which it will fail, or any possible side-effects (assuming the type found is correct) ?
  • If the value parameter of ValueToTypeMap is not found and no default type is given, I get a compile error from the static_assert, as I intended, though the error message from the compiler is pretty verbose, and the message from the static_assert is buried in many lines of error. Is there any less verbose solution to this ? Update: just tested with clang on wandbox, which is a bit less verbose, and also put the static_assert error on the first line. The many lines of error I had originally were with g++ in mingw64. Update 2: even with g++ the errors are less verbose now, I'm not sure what I have done which changed that.
  • Any other suggestion is welcome
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.