4

In C++11/C++14,

template <
   typename T ,
   template <typename...> class Container_t
>
void MyFunc(Container_t<T> &data) { ... }


template <typename T>
void MyFunc2( T v ) { ... }


int main()
{
   std::vector<char> v;

   MyFunc<char, std::vector>(v);     // OK
   MyFunc(v);                        // error

   int i;

   MyFunc2<int>(i);                  // OK
   MyFunc2(i);                       // OK
}

I get an error with MyFunc(v).

Is it possible in any way to let the compiler find out the type of the container passed to the variadic template function? I can see no problems in finding it out, as with a normal types in normal templates.

If I need to change the type of v, do I have to fix all the calls to MyFunc?

Compiler: Microsoft Visual C++ 2015 (v140)

O'Neil
  • 3,790
  • 4
  • 16
  • 30
Pietro
  • 12,086
  • 26
  • 100
  • 193

3 Answers3

8

Instead of trying to deduce container type, assume container defines what type it stores.

template <typename Container>
void MyFunc(Container& data)
{ 
   // all std containers defines value_type member (also reference etc.)
   using std::begin;
   using value_type = typename Container::value_type;
   value_type value = *begin(data);
  ...
}

Please note, that you might not need type of stored elements at all:

template <typename Container>
void MyFunc(Container& data)
{ 
   using std::begin;
   auto value = *begin(data);
  ...
}

if you want to work on std containers only (or the ones with similar templates arguments) - see Richard Hodges answer.

Community
  • 1
  • 1
Hcorg
  • 11,598
  • 3
  • 31
  • 36
  • 2
    Use `using std::begin; using value_type = *begin(data);` will work with C-arrays and with extensions on non-`.begin()` capable containers. – Yakk - Adam Nevraumont May 19 '16 at 17:27
  • Pretty sure @Yakk meant to not use `::value_type` at all. – Barry May 19 '16 at 17:36
  • D'oh, I meant `using value_type = std::decay_t;`, which extracts the value type from what the container returns directly. but yes, `auto value=*begin(data);` works, with `using value_type=decltype(value);` if you need to use the type elsewhere. – Yakk - Adam Nevraumont May 19 '16 at 17:51
5

The trick is to name the template's template arguments:

#include <vector>
#include <iostream>
#include <typeinfo>

template <
   typename T ,
   typename A,
   template <typename = T, typename = A> class Container_t
>
void MyFunc(Container_t<T, A> &data) { 
     std::cout << "value type = " << typeid(T).name() << std::endl;
     std::cout << "allocator type = " << typeid(A).name() << std::endl;
     std::cout << "container type = " << typeid(Container_t<T,A>).name() << std::endl;
   }


template <typename T>
void MyFunc2( T v ) {  }


int main()
{
   std::vector<char> v;

   MyFunc<char, std::allocator<char>, std::vector>(v);     // OK
   MyFunc(v);                        // now ok

}

If you don't care about anything except the value type and the container...

#include <vector>
#include <map>
#include <iostream>
#include <typeinfo>

template <
   typename T ,
   typename...Rest,
   template <typename, typename...> class Container_t
>
void MyFunc(Container_t<T, Rest...> &data) { 
     std::cout << "value type = " << typeid(T).name() << std::endl;
     std::cout << "container type = " << typeid(Container_t<T,Rest...>).name() << std::endl;
   }


template <typename T>
void MyFunc2( T v ) {  }


int main()
{
   std::vector<char> v;
   std::map<char, int> m;

//   MyFunc<char, std::allocator<char>, std::vector>(v);     // OK
   MyFunc(v);                        // now ok
   MyFunc(m);                        // now ok

}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • @101010 updated the answer to allow variadic args in the container. – Richard Hodges May 19 '16 at 17:33
  • 1
    It's worth noting the curious restrictions of this approach: The value type must be the first template parameter, and it doesn't work for things like `std::array`. That's why template template parameters are really not all that useful in this situation, and you're much better off deducing the type as a whole and getting at its traits in other ways. – Kerrek SB May 19 '16 at 17:42
  • @Richard, is it possible to do the same with member functions? – Pietro May 19 '16 at 17:47
  • why do you default type template parameters of a template template parameter? – Piotr Skotnicki May 19 '16 at 18:32
2

My guess is that it's a VC++ bug since GCC and CLANG can deduce the input template parameter. As proposed by KerrekSB in the comments a less painful work-around would be the following:

template<typename T, template<typename...> class Container_t, typename... Args>
void MyFunc(Container_t<T, Args...> &data) { 
  ...
}

Live Demo

101010
  • 41,839
  • 11
  • 94
  • 168