Search code examples
c++templatestemplate-templates

Template argument that takes templates as well (nested templates)


I am trying to avoid code duplication, for the following thing:

template<class StringType, typename type1, typename type2, class MapType>
void readDatastructure(MapType<type1, type2> map, const StringType path) {
... some code
}

I have several different map types that have different type1 and type2 parameters. At some point I want to statically check what types are type1 and type2 and I can't figure out how to get this thing to compile. I have tried couple of variations on that template declaration and none of them seem to work. Is that even possible?

Cheers,


Solution

  • You want something along the lines of

    template<class StringType, typename type1, typename type2,
             template<class, class> class MapType>
    void readDatastructure(MapType<type1, type2> map, const StringType path);
    

    But this won't work for, say, std::map, which has two extra template parameters (for the comparator and the allocator). So you either have to do

    template<class StringType, typename type1, typename type2,
             template<class...> class MapType>
    void readDatastructure(MapType<type1, type2> map, const StringType path);
    

    or add extra template parameters, i.e.,

    template<class StringType, typename type1, typename type2, class C, class A,
             template<class, class, class, class> class MapType>
    void readDatastructure(MapType<type1, type2, C, A> map, const StringType path);
    

    The first still doesn't work with a std::map taking non-default comparator/allocators; the second doesn't work if your map doesn't have exactly four template type parameters - e.g. unordered_map, which has five.

    Thus, it's probably better to have your map type publish those types. std::map, for instance, publishes them as key_type and mapped_type. By "publish", I mean defining them as member typedefs. Then you can write

    template<class StringType, class MapType>
    void readDatastructure(MapType map, const StringType path);
    

    and use e.g., typename MapType::key_type in place of type1.

    If you can't change your map types, and they don't follow the standard protocol, you can write a traits class and specialize it for your map types:

    template<class T>
    struct map_traits {
        using key_type = typename T::key_type;
        using mapped_type = typename T::mapped_type;
    };
    template<class T1, class T2>
    struct map_traits<MyBrokenMap<T1, T2>> {
        using key_type = T1;
        using mapped_type = T2;
    };
    

    Then you can use typename map_traits<MapType>::key_type etc.