How to provide std::map as an argument to a template template parameter
Asked Answered
S

2

8

I have a class that needs to use some sort of map. By default, I want to use std::map, but I also want to give the user the ability to use something different if they want (e.g. std::unordered_map or maybe even a user created one).

So I have code that looks like

#include <map>

template<class Key, template<class, class> class Map = std::map>
class MyClass {
};

int main() {
  MyClass<int> mc;
}

But then, g++ complains

test.cpp:3:61: error: template template argument has different template parameters than its corresponding template template parameter
template<class Key, template<class, class> class Map = std::map>
                                                            ^
test.cpp:8:14: note: while checking a default template argument used here
  MyClass<int> mc;
  ~~~~~~~~~~~^
/Library/Developer/CommandLineTools/usr/bin/../include/c++/v1/map:781:1: note: too many template parameters in template template argument
template <class _Key, class _Tp, class _Compare = less<_Key>,
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
test.cpp:3:21: note: previous template template parameter is here
template<class Key, template<class, class> class Map = std::map>
                    ^~~~~~~~~~~~~~~~~~~~~~
1 error generated.

So it looks like g++ is unhappy that std::map has default arguments.

Is there a way I can allow Map to be any sort of template that can accept at least two template arguments?

I would prefer to stick with C++98 if I can, but I'm open to C++11.

Subadar answered 6/12, 2014 at 15:24 Comment(3)
If you're open to C++11, you can say template <typename...> class Map = std::map, but the real solution is to parametrize on the type, not on a template.Marden
@KerrekSB I had originally done it that way, but I need both Map<Key,Key> and Map<Key,int>. I also have default template argument for these too, but I thought it would be convenient if the user could just specify Map and if they didn't specify Map<Key,Key> or Map<Key,int>, those would be determined automatically. Is there a better way about this avoiding template template parameters?Subadar
Possible duplicate of C++ template function default value.Milreis
P
9

The problem is that your template template parameter has only two template parameters, as opposed to map, which has four.

template<class Key, template<class, class, class, class> class Map = std::map>
class MyClass {
};

Or

template<class Key, template<class...> class Map = std::map>
class MyClass {
};

Should compile.
However, to avoid such problems, try to take the map type instead, and extract the key type via the corresponding member typedef. E.g.

template <class Map>
class MyClass {
    using key_type = typename Map::key_type;
};
Pliant answered 6/12, 2014 at 15:27 Comment(7)
Thank you! g++ is happy. However, this forbids Map types that optionally accept template template parameters or any parameters other than types right? Out of curiosity is there a way to allow Map's optional arguments to be anything?Subadar
@0x499602D2 That's exactly what I propose in the last sentence of my post.Pliant
@Subadar No, that is impossible. (There is also a popular unanswered SO post about exactly this question somewhere...) That's why you should take the map type and no template. It's a hell lot more flexible.Pliant
@0x499602D2 Source? Cannot see anything in the post-Urbana mailing.Pliant
@0x499602D2 Those have both nothing to do with taking template arguments of arbitrary kind.Pliant
@Pliant So they wouldn't take both non-type and type parameters?Etherealize
@0x499602D2 Of course not. Only non-type arguments whose type is to be deducedPliant
M
4

Your code will compile in C++17. A long-standing defect report of the C++ Core Working Group (CWG 150) was resolved (by P0522R0) in time for C++17.

cppreference.com also discuss this here, and include a helpful example:

template<class T> class A { /* ... */ };
template<class T, class U = T> class B { /* ... */ };
template <class ...Types> class C { /* ... */ };

template<template<class> class P> class X { /* ... */ };
X<A> xa; // OK
X<B> xb; // OK in C++17 after CWG 150
         // Error earlier: not an exact match
X<C> xc; // OK in C++17 after CWG 150
         // Error earlier: not an exact match

Testing with my version of GCC (8.3.0), I find that using the -std=c++17 flag will successfully compile your program; while using earlier versions of C++ (e.g. -std=c++14 or -std=c++11) will fail.

Monniemono answered 11/8, 2019 at 18:10 Comment(1)
As far as I know clang did not implement it since it was apparently a breaking change. Did that change and did the issue with template partial ordering got fix?Sorensen

© 2022 - 2024 — McMap. All rights reserved.