Creating a variadic template map of type key to value with nondefault constructor

Viewed 34

I'm trying to create a map that can work as such:

// I have A, B, and a templated class Processor
struct A;
struct B;

template<class T>
struct Processor {
    Processor(T foo);
    void SomeFunction();
};


// This is something like how I expect the map to be able to work
int main(){
    A a;
    B b;

    TypeToValMap<KeyValue<A, Processor<A>>,
                 KeyValue<B, Processor<B>>> myMap{{a}, {b}}; // a and b will be passed to the constructors of the Processors
    
    myMap.get<A>().SomeFunction(); // gets Processor<A> that was constructed with a
    myMap.get<B>().SomeFunction(); // gets Processor<B> that was constructed with b

}

I was trying to implement it somewhere along the lines of

template<class Key, class Value>
struct KeyValue{
    using key = Key;
    using value = Value;
    value val;
};

template<typename... Args>
struct TypeToValMap {
    TypeToValMap(Args&&... args) {
            //???
    }
    
    // This function should return the value by using T as the key
    template<class T>
    constexpr auto get(){ // ???? }
    
    // Needs some container to hold the values?
};

I was trying to use something like

using Procs = std::variant<Processor<A>, Processor<B>>;
std::array<Procs, sizeof...(Args)>  

or

std::array<std::any, sizeof...(Args)>  

to hold the values, but couldn't get it working. Is it possible to implement such a thing?

1 Answers

Since you know all the types at compile time, you can store them in a container like std::tuple.

If your mapping is always T -> Processor<T>, you can use a class to wrap the tuple like this:

template<class... T>
struct ProcessorMap : private std::tuple<Processor<T>...> {
private:
    using tuple = std::tuple<Processor<T>...>;
public:
    using tuple::tuple;  // Use std::tuple constructors

    template<class U>
    decltype(auto) get() & {
        return std::get<Processor<U>>(static_cast<tuple&>(*this));
    }
    template<class U>
    decltype(auto) get() const& {
        return std::get<Processor<U>>(static_cast<const tuple&>(*this));
    }
    template<class U>
    decltype(auto) get() && {
        return std::get<Processor<U>>(static_cast<tuple&&>(*this));
    }
};

(Full example: https://godbolt.org/z/h7bK63qYh)

If your KeyValue pairs are not so simple, you can convert a key to a value at compile time (Here, with lookup<Key, KeyValuePairs...>)

template<class Key, class Value>
struct KeyValue {
    using key = Key;
    using value = Value;
};

template<class Key, class... Pairs>
struct lookup;

template<class Key, class Value, class... Pairs>
struct lookup<Key, KeyValue<Key, Value>, Pairs...> { using type = Value; };
template<class Key, class MismatchedKey, class Value, class... Pairs>
struct lookup<Key, KeyValue<MismatchedKey, Value>, Pairs...> : lookup<Key, Pairs...> {};
template<class Key> struct lookup<Key> {};  // Not found

template<class... Pairs>
struct ProcessorMap : private std::tuple<typename Pairs::value...> {
private:
    using tuple = std::tuple<typename Pairs::value...>;
public:
    using tuple::tuple;  // Use std::tuple constructors

    template<class U>
    decltype(auto) get() & {
        return std::get<typename lookup<U, Pairs...>::type>(static_cast<tuple&>(*this));
    }
    template<class U>
    decltype(auto) get() const& {
        return std::get<typename lookup<U, Pairs...>::type>(static_cast<const tuple&>(*this));
    }
    template<class U>
    decltype(auto) get() && {
        return std::get<typename lookup<U, Pairs...>::type>(static_cast<tuple&&>(*this));
    }
};

Which is essentially the same, replacing Processor<U> with lookup<U, Pairs...>::type. Full example: https://godbolt.org/z/bc3KKcvj8

Related