2
votes

I am trying to write function which is roughly similar to a function that takes two instances of containers (let's say for now they are the same), containing same type and merges them. So here's my function for vectors:

template<typename T>
void test(std::vector<T> v1, std::vector<T> v2) {
  std::cout << "called nested for c\n";
}

and it works.

Now I want to have one that would work for sets or vectors. And I try this:

template<typename T, template <typename> typename C >
void test(C<T> v1, C<T> v2) {
  std::cout << "called nested for c<t>\n";
}

and get

nestedt.cc:33:6: note: template argument deduction/substitution failed: nestedt.cc:43:12: error: wrong number of template arguments (3, should be 1) test(s, s); ^ nestedt.cc:32:51: note: provided for ‘template class C’ template typename C > ^

I try

template< template <typename T> typename C >
void test(C<T> v1, C<T> v2) {}

std::set<int> s = {1,2};
test(s, s);

and that doesn't work:

nestedt.cc:32:6: note: template argument deduction/substitution failed: nestedt.cc:42:12: note: cannot convert ‘s’ (type ‘std::set’) to type ‘int’ test(s, s); ^

So I try

template<typename C, typename T >
void test(C<T> v1, C<T> v2) {}

and get

nestedt.cc:32:6: note: candidate: template void test(C, C) void test(C v1, C v2) { ^ nestedt.cc:32:6: note: template argument deduction/substitution failed: nestedt.cc:42:12: note: couldn't deduce template parameter ‘T’ test(s, s); ^

I feel like I'm not even close to understanding how templates actually work in C++ and it is so sad. Also in reality I want the containers to be different. And ideally be able to somehow specify a limited set of allowed containers. And they are not really std:: containers.

Note that the final goal is to be able to take 2 different containers, so something like test(vector<int>, set<int>). And modifying the containers is kind of impossible.

3
Why not just template<typename C>void test(C v1, C v2) {}?NathanOliver
Because I really want test(<C1<T> v1, C2<T> v2), 2 different containers containing the same values.MK.
They would be. You wouyld be able to call test(vector<int>, set<int>). It would only compile if the same container type is used. I think I get where you are going though and will write up an answer that will show you how you can constrain the template.NathanOliver
@NathanOliver I think you misread. I want to allow it to be called with different containers.MK.
Passing an iterator would suffice for the function's purpose? Suppose you're using std::vector and std::list, they implement ForwardIterator and you can use the post-increment operator on it.jweyrich

3 Answers

2
votes

Your first attempt was nearly right. You got the error because the standard containers like vector accept more than one template parameter. For example, this is std::vector:

template<
    class T,
    class Allocator = std::allocator<T>
> class vector;

You never usually use the second parameter, and it has a default, but it's still present, so your template template parameter template <typename> typename C can't match. Instead, you can do this:

template<template <typename...> typename C, typename... Ts>
void test(C<Ts...> v1, C<Ts...> v2) {}
2
votes

I believe what you are looking for is

template<template <typename...> typename Cont1, 
         template <typename...> typename Cont2,
         typename... T>
void test(Cont1<T...> a, Cont2<T...> b) { }

In the above template <typename...> typename Cont1 declares one template template type and template <typename...> typename Cont2 declares another one so you can have two different container types. The both share T... so each template container needs to have a matching template parameter list. This means

test(std::map<int, int>{}, std::map<int, int>{});
test(std::set<int>{}, std::set<int>{});
test(std::vector<int>{}, std::set<int>{});

all work but

test(std::map<int, int>{}, std::vector<int>{});
test(std::map<int, int>{}, std::set<int>{});

wouldn't.

2
votes

You need at least 2 template parameters:

  1. Container type
  2. Contained type

Since you're using an STL container and passing the contained type apart from the container, the compiler cannot infer the other parameters (e.g., allocator std::allocator<T>, compare std::less<T>). But you can forward all nested templated parameters using a variadic template. Example:

template <template <typename...> class C, typename... T>
struct wrapper {
    typedef C<T...> type;
};
wrapper<std::vector, int>::type foo;
wrapper<std::set   , int>::type bar;

You can see it in ideone.