27

Assume I have a template (called ExampleTemplate) that takes two arguments: a container type (e.g. list, vector) and a contained type (e.g. float, bool, etc). Since containers are in fact templates, this template has a template param. This is what I had to write:

#include <vector>
#include <list>

using namespace std;

template < template <class,class> class C, typename T>
class ExampleTemplate {
    C<T,allocator<T> > items;
public:
    ....
};

main()
{
    ExampleTemplate<list,int> a;
    ExampleTemplate<vector,float> b;
}

You may ask what is the "allocator" thing about. Well, Initially, I tried the obvious thing...

template < template <class> class C, typename T>
class ExampleTemplate {
    C<T> items;
};

...but I unfortunately found out that the default argument of the allocator...

   vector<T, Alloc>
   list<T, Alloc>
   etc

...had to be explicitely "reserved" in the template declaration. This, as you can see, makes the code uglier, and forces me to reproduce the default values of the template arguments (in this case, the allocator).

Which is BAD.

EDIT: The question is not about the specific problem of containers - it is about "Default values in templates with template arguments", and the above is just an example. Answers depending on the knowledge that STL containers have a "::value_type" are not what I am after. Think of the generic problem: if I need to use a template argument C in a template ExampleTemplate, then in the body of ExampleTemplate, do I have to reproduce the default arguments of C when I use it? If I have to, doesn't that introduce unnecessary repetition and other problems (in this case, where C is an STL container, portability issues - e.g. "allocator" )?

ttsiodras
  • 10,602
  • 6
  • 55
  • 71
  • It gets worse, your code doesn’t work on all compilers since the standard library containers may have (and *do*, in some implementations) even more template arguments with standard values. This code is effectively not portable. – Konrad Rudolph Mar 14 '11 at 16:57
  • Agreed. I do hope I won't have to resort to macros... God, anything but macros... – ttsiodras Mar 14 '11 at 16:59
  • If you gave us a little more insight into what you're actually trying to do, it might help to branch out of your very specific question. What you're asking for is not possible. – Mike Mar 14 '11 at 17:10
  • @Mike: "What I am asking for is not possible" - if I understand correctly, you mean that when I pass a template argument C to a template (MyExample), in the body of MyExample I will *have* to manually reproduce the default arguments of C. Is that what you are saying? – ttsiodras Mar 14 '11 at 17:14
  • @Konrad, I thought [extra template arguments weren't allowed](http://stackoverflow.com/q/1469743/33732) precisely *because* of this use case with template template parameters. – Rob Kennedy Mar 14 '11 at 18:47
  • @Rob thanks for the info. I distinctly remember seeing a violation of this in one stdlibc++ implementation (of `std::list` IIRC) but I can’t recall where exactly and my memory may deceive me. – Konrad Rudolph Mar 14 '11 at 18:54
  • @ttsiodras That's what I'm saying. Unless the container specifies the default type for a template parameter in some consistent way that you use, you're out of luck. There's no way to refer to the default type parameters of a template. See my updated answer below. – Mike Mar 14 '11 at 20:44

6 Answers6

14

Perhaps you'd prefer this:

#include <vector>
#include <list>

using namespace std;

template <class Container>
class ForExamplePurposes {
    typedef typename Container::value_type T;
    Container items;
public:
};

int main()
{
    ForExamplePurposes< list<int> > a;
    ForExamplePurposes< vector<float> > b;
}

This uses "static duck typing". It is also a bit more flexible as it doesn't force the Container type to support STL's Allocator concept.


Perhaps using the type traits idiom can give you a way out:

#include <vector>
#include <list>

using namespace std;

struct MyFunkyContainer
{
    typedef int funky_type;
    // ... rest of custom container declaration
};

// General case assumes STL-compatible container
template <class Container>
struct ValueTypeOf
{
    typedef typename Container::value_type type;
};

// Specialization for MyFunkyContainer
template <>
struct ValueTypeOf<MyFunkyContainer>
{
    typedef MyFunkyContainer::funky_type type;
};


template <class Container>
class ForExamplePurposes {
    typedef typename ValueTypeOf<Container>::type T;
    Container items;
public:
};

int main()
{
    ForExamplePurposes< list<int> > a;
    ForExamplePurposes< vector<float> > b;
    ForExamplePurposes< MyFunkyContainer > c;
}

Someone who wants to use ForExamplePurposes with a non-STL-compliant container would need to specialize the ValueTypeOf traits class.

Emile Cormier
  • 28,391
  • 15
  • 94
  • 122
  • This depends on the knowledge that STL containers can provide access to the contained type via ::value_type. Thanks, but the above was just an example - think about the generic problem, where you have no such "shortcut", and need to pass a template to a template. Is there no way to do it without manually reproducing the default arguments? – ttsiodras Mar 14 '11 at 17:03
  • @ttsiofras: FYI, all STL containers are required by the standard to define the `value_type` nested type. – Emile Cormier Mar 14 '11 at 17:08
  • 1
    Agreed, but again, my question was not about STL containers (that was just an example). It is about template arguments to templates. – ttsiodras Mar 14 '11 at 17:11
  • @ttsiodras: I think you need to think of a different example and append it your question (leave the old example there, so that people don't think I'm nuts). – Emile Cormier Mar 14 '11 at 17:16
  • @ttsiodras: I've thought of another trick that might give you a way out. See updated answer. – Emile Cormier Mar 14 '11 at 17:31
  • Your updated response includes custom type_traits, that implement the value_type functionality in a non-STL dependent way. So your response to the generic problem is this: In ExampleTemplate, if parameter A is in fact a template, and we want to avoid manually reproducing the default arguments of template A whenever A is instantiated inside the body of ExampleTemplate, the only way is to use type_traits, like STL does. Is that what you are saying? – ttsiodras Mar 14 '11 at 17:35
  • @ttsiodras: It might not be the only way, but it is one way. I don't see any other way, but then again, I'm not a metaprogramming guru. I am not talking about `std::type_traits` here, but your very own custom type traits class suited for your particular problem. `ValueTypeOf` in my response is an example of such a type traits class. – Emile Cormier Mar 14 '11 at 17:45
  • I appreciate your efforts in answering this - your answer boils down to... "instantiate the "difficult" template at call time, so that you get to use the default arguments in an easy manner, and use type_traits (ready made or custom made) to pass whatever extra information you need". Agreed? – ttsiodras Mar 14 '11 at 18:30
  • @ttsiodras: Why do you want me to boil it down? Are you answering an interview or homework question? – Emile Cormier Mar 14 '11 at 19:00
  • Homework? I wish I was that young again :-) I do believe IT education is Java-specific these days :-) No, the only reason I asked this is because I always try to "distill" information in forms that are easily kept in memory. You don't have to verify my last statement, since this did (see the 2nd answer, http://stackoverflow.com/q/1469743/33732) – ttsiodras Mar 15 '11 at 06:20
  • Emile : nice answer. it can be improved using SFINAE to auto-detect funky containers. Eliminating the need to specific partial specialization. To do this auto-detection you would need to add a template parameter that is deduced automatically using a default value declared like that: `T::value_type`. And if that can't be compiled, the compiler won't make an error because SFINAE, and will use your second definition with a second template parameter that is more permissive and then the FunkyContainers will use that second definition automatically. – v.oddou Apr 12 '13 at 09:23
5

I would propose to create adapters.

Your class should be created with the exact level of personalization that is required by the class:

template <template <class> C, template T>
class Example
{
  typedef T Type;
  typedef C<T> Container;
};

EDIT: attempting to provide more is nice, but doomed to fail, look at the various expansions:

  • std::vector<T>: std::vector<T, std::allocator<T>>
  • std::stack<T>: std::stack<T, std::deque<T>>
  • std::set<T>: std::set<T, std::less<T>, std::allocator<T>>

The second is an adapter, and so does not take an allocator, and the third does not have the same arity. You need therefore to put the onus on the user.

If a user wishes to use it with a type that does not respect the expressed arity, then the simplest way for him is to provide (locally) an adapter:

template <typename T>
using Vector = std::vector<T>; // C++0x

Example<Vector, bool> example;

I am wondering about the use of parameter packs (variadic templates) here... I don't know if declaring C as template <class...> C would do the trick or if the compiler would require a variadic class then.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • I must be missing something - Matthieu, I can't see how your first code section would compile, since if I pass Example, I will get a compilation error at the C instantiation. – ttsiodras Mar 14 '11 at 18:20
  • @ttsiodras: it's a single example :) Indeed the first example is not usable as is, but the issue is more general. You cannot guess the default template parameters, for example, for a `stack`, the default instanciation would be `std::stack>`, note how the second parameter is not a `std::allocator` ? Therefore, you need to ask the user to provide a class that fits, and it is her responsability to do so. The second part shows the C++0x to accomplish it easily, using the aliasing of templates. – Matthieu M. Mar 14 '11 at 19:56
  • @ttsiodras: I've edited to make it clearer, and expose the issues that wanting to be 'nice' pose. It's annoying that it does not work out of the box, but it is also reminiscent of pointer to functions and functions with default parameters: you need create an adapter (a thunk) for the signature to match. – Matthieu M. Mar 14 '11 at 20:00
3

You have to give the full template signature, including default parameters, if you want to be able to use the template template parameter the usual way.

template <typename T, template <class U, class V = allocator<U> > class C>
class ExampleTemplate {
    C<T> items;
public:
    ....
};

If you want to handle other containers that the one from the STL, you can delegate container construction to a helper.

// Other specialization failed. Instantiate a std::vector.
template <typename T, typename C>
struct make_container_
{
    typedef std::vector<T> result;
};

// STL containers
template <typename T, template <class U, class V = allocator<U> > class C>
struct make_container_<T,C>
{
    typedef C<T> result;
};

// Other specializations
...

template <typename T, typename C>
class ExampleTemplate {
    make_container_<T,C>::result items;
public:
    ....
};
log0
  • 10,489
  • 4
  • 28
  • 62
1

I think, it is required to reproduce all template parameters, even default. Note, that Standard itself does not use template template parameters for containter adaptors, and prefers to use regular template parameters:

template < class T , class Container = deque <T > > class queue { ... };
template < class T , class Container = vector <T>, class Compare = less < typename Container :: value_type > > class priority_queue { ... };
Konstantin Tenzin
  • 12,398
  • 3
  • 22
  • 20
0

As the question exactly described the problem I had in my code (--I'm using Visual Studio 2015), I figured out an alternative solution which I wanted to share.

The idea is the following: instead of passing a template template parameter to the ExampleTemplate class template, one can also pass a normal typename which contains a type DummyType as dummy parameter, say std::vector<DummyType>.

Then, inside the class, one replace this dummy parameter by something reasonable. For replacement of the typethe following helper classes can be used:

// this is simply the replacement for a normal type:
// it takes a type T, and possibly replaces it with ReplaceByType
template<typename T, typename ReplaceWhatType, typename ReplaceByType>
struct replace_type
{
    using type = std::conditional_t<std::is_same<T, ReplaceWhatType>::value, ReplaceByType, T>;    
};

// this sets up the recursion, such that replacement also happens
// in contained nested types
// example: in "std::vector<T, allocator<T> >", both T's are replaced
template<template<typename ...> class C, typename ... Args, typename ReplaceWhatType, typename ReplaceByType>
struct replace_type<C<Args ...>, ReplaceWhatType, ReplaceByType>
{
    using type = C<typename replace_type<Args, ReplaceWhatType, ReplaceByType>::type ...>;
};

// an alias for convenience
template<typename ... Args>
using replace_type_t = typename replace_type<Args ...>::type;

Note the recursive step in replace_type, which takes care that types nested in other classes are replaced as well -- with this, for example, in std::vector<T, allocator<T> >, both T's are replaced and not only the first one. The same goes for more than one nesting hierarchy.

Next, you can use this in your ExampleTemplate-class,

struct DummyType {};

template <typename C, typename T>
struct ExampleTemplate
{
    replace_type_t<C, DummyType, T> items;
};

and call it via

int main()
{
    ExampleTemplate<std::vector<DummyType>, float> a;
    a.items.push_back(1.0);
    //a.items.push_back("Hello");  // prints an error message which shows that DummyType is replaced correctly

    ExampleTemplate<std::list<DummyType>, float> b;
    b.items.push_back(1.0);
    //b.items.push_back("Hello");  // prints an error message which shows that DummyType is replaced correctly

    ExampleTemplate<std::map<int, DummyType>, float> c;
    c.items[0]=1.0;
    //c.items[0]="Hello";          // prints an error message which shows that DummyType is replaced correctly
}

DEMO

Beside the not-that-nice syntac, this has the advantage that

  1. It works with any number of default template parameters -- for instance, consider the case with std::map in the example.

  2. There is no need to explicitly specify any default template parameters whatsoever.

  3. It can be easily extended to more dummy parameters (whereas then it probably should not be called by users ...).

By the way: Instead of the dummy type you can also use the std::placeholder's ... just realized that it might be a bit nicer.

davidhigh
  • 14,652
  • 2
  • 44
  • 75
0

The following code will allow you to do something like you're asking for. Of course, this won't work with standard containers, since this has to already be part of the template class that's being passed into the template.


/* Allows you to create template classes that allow users to specify only some
 * of the default parameters, and some not.
 *
 * Example:
 *  template <typename A = use_default, typename B = use_default>
 *  class foo
 *  {
 *              typedef use_default_param<A, int> a_type;
 *              typedef use_default_param<B, double> b_type;
 *              ...
 *  };
 *
 *  foo<use_default, bool> x;
 *  foo<char, use_default> y;
 */

struct use_default;

template<class param, class default_type>
struct default_param
{
        typedef param type;
};

template<class default_type>
struct default_param<use_default, default_type>
{
        typedef default_type type;
};

But I don't really think this is what you're looking for. What you're doing with the containers is unlikely to be applicable to arbitrary containers as many of them will have the problem you're having with multiple default parameters with non-obvious types as defaults.

Mike
  • 693
  • 3
  • 13