Search code examples
c++variadic-templates

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


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?


Solution

  • 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