This question already has an answer here:
partial type as template argument c++ [duplicate]
(1 answer)
Closed 6 years ago.
I'm trying to write an algorithm that should work with different containers (std::vector, QVector) containing the same type:
template<class Container>
boolean findpeaks(cv::Mat &m, Container<std::pair<int, double>> &peaks) {
// do stuff
peaks.push_back(std::make_pair(1, 1.0));
return true;
}
This one gives me
'Container' is not a template
template<template<typename> class Container>
I get:
error: no matching function for call to 'findpeaks(cv::MatExpr, std::vector >&)'
...
note: template argument deduction/substitution failed:
error: wrong number of template arguments (2, should be 1)
Calling code:
cv::Mat m(data, true);
std::vector<std::pair<int, double>> peaks;
QVERIFY(daf::findpeaks(m.t(), peaks));
I've also tried something like this:
template<template< template<typename, typename> typename > class Container>
warning: ISO C++ forbids typename key in template template parameter; use -std=c++1z or -std=gnu++1z [-Wpedantic]
And some more errors...
std::vector has two template parameters.
template<
class T,
class Allocator = std::allocator<T>
> class vector;
And QVector has one. You can do it with variadic template:
template<template <typename...> class Container>
bool findpeaks(cv::Mat &m, Container<std::pair<int, double>> &peaks) {
// do stuff
peaks.push_back(std::make_pair(1, 1.0));
return true;
}
Do you actually need Container to be a class template? Just make it a normal type:
template<class Container>
boolean findpeaks(cv::Mat &m, Container& peaks) {
// do stuff
peaks.push_back(std::make_pair(1, 1.0));
return true;
}
This will let you use other containers that are potentially not templates. Like, struct MySpecialPairContainer { ... };
You may do
template<template <typename ...> class Container>
bool findpeaks(cv::Mat &m, Container<std::pair<int, double>> &peaks) {
// do stuff
peaks.push_back(std::make_pair(1, 1.0));
return true;
}
Your problem is that std::vector has 2 template parameters, the type T and an allocator.
But you can do even simpler:
template<typename Container>
bool findpeaks(cv::Mat& m, Container& peaks) {
// do stuff
peaks.push_back(std::make_pair(1, 1.0));
return true;
}
Just as expressed with #Barry's answer, I don't think you need a template template parameter here.
However, you seem to have concern about expressing "a container that supports push_back with a pair of..."
I suggest you to express this constraint in a sfinae constraint expression instead. Even if your parameter is explicitly requiring std::pair to be in the first template parameter of a class, it doesn't mean it has a push_back function, and doesn't mean the supposedly existing push_back is taking a std::pair as parameter.
Arguments of a function is currently a bad way of expressing what a template type should be able to do, or should be. You'll have to wait for concepts for that.
In the meantime, you can use a sfinae constraint in your function signature that explicitly express that you need a type that has a member push_back function that accept a std::pair:
template<class Container>
auto findpeaks(cv::Mat &m, Container& peaks)
// Using trailing return type
-> first_t<bool, decltype(peaks.push_back(std::make_pair(1, 1.0)))>
// Here's the constraint -^ that expression need to be valid
{
// do stuff
peaks.push_back(std::make_pair(1, 1.0));
return true;
}
first_t can be implemented that way:
template<typename T, typename...>
using first_t = T;
For the function to exist, the expression inside the decltype must be valid. If the contraint is not satified, the compiler will try other overloads of the function findpeaks.
In general, you shouldn't over specify. If I wrote:
struct my_thing {
void push_back( std::pair<int, double> const& ) {}
};
shouldn't I be able to pass my_thing to your findpeaks?
There is absolutely no need for the template<class...>class Container template within the function, so requring it in the interface is an overspecification.
What you need is a sink (graph theoretical sink -- a sink is where things flow into, and don't flow out of) that consumes pairs of int, double. Ideally you want to be able to pass in a container without extra boilerplate.
template<class T>
struct sink:std::function<void(T)> {
using std::function<T>::function;
// more
};
now your function looks like:
bool findpeaks(cv::Mat &m, sink<std::pair<int, double>const&> peaks) {
// do stuff
peaks(std::make_pair(1, 1.0));
return true;
}
and as a bonus, you can now put it into a cpp file instead of a header. (The dispatching costs for a std::function are modest).
This does require that you wrap the second parameter up at the call site:
std::vector<std::pair<int, double>> v;
if(findpeaks( matrix, [&](auto&& e){v.push_back(decltype(e)(e));} ) {
// ...
which you might not like. Because we didn't use a naked std::function but instead a sink, we can get around this. First we write a metatrait, then some traits.
namespace details {
template<template<class...>class Z, class alwaysvoid, class...Ts>
struct can_apply:std::false_type{};
template<template<class...>class Z, class...Ts>
struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...>:std::true_type{};
}
template<template<class...>class Z, class...Ts>
using can_apply=details::can_apply<Z, void, Ts...>;
This is a meta trait that lets us write other traits.
template<class C, class X>
using push_back_result = decltype( std::declval<C>().push_back( std::declval<X>() ) );
template<class C, class X>
using can_push_back = can_apply< push_back_result, C, X >;
now we have a trait can_push_back that is true_type if and only if you can push an X into the container C.
We now augment sink:
template<class T, class Base=std::function<void(T)>>
struct sink:Base {
using Base::Base;
template<class C,
std::enable_if_t< can_push_back< C&, T >{}, int> =0
>
sink( C& c ):
Base( [&](auto&& t){ c.push_back(decltype(t)(t)); } )
{}
sink()=default;
sink(sink const&)=default;
sink(sink &&)=default;
sink& operator=(sink const&)=default;
sink& operator=(sink &&)=default;
};
This newly augmented sink now can be passed a container that supports .push_back(T) and automatically writes a function that solves the problem for you.
std::vector<std::pair<int, double>> v;
if(findpeaks( matrix, v ) {
// ...
That just works.
We can do the same for containers that support .insert(T), and after that we can use std::set<T> or even std::map<int, double> as a sink for your algorithm:
std::set<std::pair<int, double>> s;
if(findpeaks( matrix, s ) {
// ...
std::map<int, double> m;
if(findpeaks( matrix, m ) {
// ...
Finally, this also supports mocking. You can write a test-sink that helps unit-test your findpeaks algorithm directly.
I find the concept of sink used sufficiently often that having a sink type that supports these kind of things makes my code clearer, and reduces its dependency on any one kind of container.
Performance wise, the cost of the type erasure in std:function is modest. if you really need performance improvement, swapping sink<X> for sink<X, F> where F is a free parameter is possible, and writing make_sink that creates a sink where Base is a lambda, should work with near zero changes in the body of the code. But before you do that, you can work on higher level optimizations, like having the output into sink be processed in a streaming manner, or fed to an async queue, or the like.
I'm trying to implement a function similar to std::transform algorithm but instead of taking the output iterator by an argument I want to create and return a container with transformed input elements.
Let's say that it's named transform_container and takes two arguments: container and functor. It should return the same container type but possibly parametrized by a different element type (the Functor can return element of different type).
I'd like to use my function as in the example below:
std::vector<int> vi{ 1, 2, 3, 4, 5 };
auto vs = transform_container(vi, [] (int i) { return std::to_string(i); });
//vs will be std::vector<std::string>
assert(vs == std::vector<std::string>({"1", "2", "3", "4", "5"}));
std::set<int> si{ 5, 10, 15 };
auto sd = transform_container(si, [] (int i) { return i / 2.; });
//sd will be of type std::set<double>
assert(sd == std::set<double>({5/2., 10/2., 15/2.}));
I was able two write two functions — one for std::set and one for std::vector — that seem to work properly. They are identical, except of the container typename. Their code is listed below.
template<typename T, typename Functor>
auto transform_container(const std::vector<T> &v, Functor &&f) -> std::vector<decltype(f(*v.begin()))>
{
std::vector<decltype(f(*v.begin()))> ret;
std::transform(std::begin(v), std::end(v), std::inserter(ret, ret.end()), f);
return ret;
}
template<typename T, typename Functor>
auto transform_container(const std::set<T> &v, Functor &&f) -> std::set<decltype(f(*v.begin()))>
{
std::set<decltype(f(*v.begin()))> ret;
std::transform(std::begin(v), std::end(v), std::inserter(ret, ret.end()), f);
return ret;
}
However, when I attempted to merge them into a single general function that works with any container, I encountered numerous issues. The set and vector are class templates, so my function template must take a template template parameter. Moreover, set and vector templates have a different number of type parameters that needs to be properly adjusted.
What is the best way to generalize the two function templates above into a function that works with any compatible container type?
Simplest cases: matching container types
For the simple case where the input type matches the output type (which I've since realized is not what you're asking about) go one level higher. Instead of specifying the type T that your container uses, and trying to specialize on a vector<T>, etc., just specify the type of the container itself:
template <typename Container, typename Functor>
Container transform_container(const Container& c, Functor &&f)
{
Container ret;
std::transform(std::begin(c), std::end(c), std::inserter(ret, std::end(ret)), f);
return ret;
}
More complexity: compatible value types
Since you want to try to change the item type stored by the container, you'll need to use a template template parameter, and modify the T to that which the returned container uses.
template <
template <typename T, typename... Ts> class Container,
typename Functor,
typename T, // <-- This is the one we'll override in the return container
typename U = std::result_of<Functor(T)>::type,
typename... Ts
>
Container<U, Ts...> transform_container(const Container<T, Ts...>& c, Functor &&f)
{
Container<U, Ts...> ret;
std::transform(std::begin(c), std::end(c), std::inserter(ret, std::end(ret)), f);
return ret;
}
What of incompatible value types?
This only gets us partway there. It works fine with a transform from signed to unsigned but, when resolving with T=int and U=std::string, and handling sets, it tries to instantiate std::set<std::string, std::less<int>, ...> and thus doesn't compile.
To fix this, we want to take an arbitrary set of parameters and replace instances of T with U, even if they are the parameters to other template parameters. Thus std::set<int, std::less<int>> should become std::set<std::string, std::less<std::string>>, and so forth. This involves some custom template meta programming, as suggested by other answers.
Template metaprogramming to the rescue
Let's create a template, name it replace_type, and have it convert T to U, and K<T> to K<U>. First let's handle the general case. If it's not a templated type, and it doesn't match T, its type shall remain K:
template <typename K, typename ...>
struct replace_type { using type = K; };
Then a specialization. If it's not a templated type, and it does match T, its type shall become U:
template <typename T, typename U>
struct replace_type<T, T, U> { using type = U; };
And finally a recursive step to handle parameters to templated types. For each type in a templated type's parameters, replace the types accordingly:
template <template <typename... Ks> class K, typename T, typename U, typename... Ks>
struct replace_type<K<Ks...>, T, U>
{
using type = K<typename replace_type<Ks, T, U>::type ...>;
};
And finally update transform_container to use replace_type:
template <
template <typename T, typename... Ts> class Container,
typename Functor,
typename T,
typename U = typename std::result_of<Functor(T)>::type,
typename... Ts,
typename Result = typename replace_type<Container<T, Ts...>, T, U>::type
>
Result transform_container(const Container<T, Ts...>& c, Functor &&f)
{
Result ret;
std::transform(std::begin(c), std::end(c), std::inserter(ret, std::end(ret)), f);
return ret;
}
Is this complete?
The problem with this approach is it is not necessarily safe. If you're converting from Container<MyCustomType> to Container<SomethingElse>, it's likely fine. But when converting from Container<builtin_type> to Container<SomethingElse> it's plausible that another template parameter shouldn't be converted from builtin_type to SomethingElse. Furthermore, alternate containers like std::map or std::array bring more problems to the party.
Handling std::map and std::unordered_map isn't too bad. The primary problem is that replace_type needs to replace more types. Not only is there a T -> U replacement, but also a std::pair<T, T2> -> std::pair<U, U2> replacement. This increases the level of concern for unwanted type replacements as there's more than a single type in flight. That said, here's what I found to work; note that in testing I needed to specify the return type of the lambda function that transformed my map's pairs:
// map-like classes are harder. You have to replace both the key and the key-value pair types
// Give a base case replacing a pair type to resolve ambiguities introduced below
template <typename T1, typename T2, typename U1, typename U2>
struct replace_type<std::pair<T1, T2>, std::pair<T1, T2>, std::pair<U1, U2>>
{
using type = std::pair<U1, U2>;
};
// Now the extended case that replaces T1->U1 and pair<T1,T2> -> pair<T2,U2>
template <template <typename...> class K, typename T1, typename T2, typename U1, typename U2, typename... Ks>
struct replace_type<K<T1, T2, Ks...>, std::pair<const T1, T2>, std::pair<const U1, U2>>
{
using type = K<U1, U2,
typename replace_type<
typename replace_type<Ks, T1, U1>::type,
std::pair<const T1, T2>,
std::pair<const U1, U2>
>::type ...
>;
};
What about std::array?
Handling std::array adds to the pain, as its template parameters cannot be deduced in the template above. As Jarod42 notes, this is due to its parameters including values instead of just types. I've gotten partway by adding specializations and introducing a helper contained_type that extracts T for me (side note, per Constructor this is better written as the much simpler typename Container::value_type and works for all types I've discussed here). Even without the std::array specializations this allows me to simplify my transform_container template to the following (this may be a win even without support for std::array):
template <typename T, size_t N, typename U>
struct replace_type<std::array<T, N>, T, U> { using type = std::array<U, N>; };
// contained_type<C>::type is T when C is vector<T, ...>, set<T, ...>, or std::array<T, N>.
// This is better written as typename C::value_type, but may be necessary for bad containers
template <typename T, typename...>
struct contained_type { };
template <template <typename ... Cs> class C, typename T, typename... Ts>
struct contained_type<C<T, Ts...>> { using type = T; };
template <typename T, size_t N>
struct contained_type<std::array<T, N>> { using type = T; };
template <
typename Container,
typename Functor,
typename T = typename contained_type<Container>::type,
typename U = typename std::result_of<Functor(T)>::type,
typename Result = typename replace_type<Container, T, U>::type
>
Result transform_container(const Container& c, Functor &&f)
{
// as above
}
However the current implementation of transform_container uses std::inserter which does not work with std::array. While it's possible to make more specializations, I'm going to leave this as a template soup exercise for an interested reader. I would personally choose to live without support for std::array in most cases.
View the cumulative live example
Full disclosure: while this approach was influenced by Ali's quoting of Kerrek SB's answer, I didn't manage to get that to work in Visual Studio 2013, so I built the above alternative myself. Many thanks to parts of Kerrek SB's original answer are still necessary, as well as to prodding and encouragement from Constructor and Jarod42.
Some remarks
The following method allows to transform containers of any type from the standard library (there is a problem with std::array, see below). The only requirement for the container is that it should use default std::allocator classes, std::less, std::equal_to and std::hash function objects. So we have 3 groups of containers from the standard library:
Containers with one non-default template type parameter (type of value):
std::vector, std::deque, std::list, std::forward_list, [std::valarray]
std::queue, std::priority_queue, std::stack
std::set, std::unordered_set
Containers with two non-default template type parameters (type of key and type of value):
std::map, std::multi_map, std::unordered_map, std::unordered_multimap
Container with two non-default parameters: type parameter (type of value) and non-type parameter (size):
std::array
Implementation
convert_container helper class convert types of known input container type (InputContainer) and output value type (OutputType) to the type of the output container(typename convert_container<InputContainer, Output>::type):
template <class InputContainer, class OutputType>
struct convert_container;
// conversion for the first group of standard containers
template <template <class...> class C, class IT, class OT>
struct convert_container<C<IT>, OT>
{
using type = C<OT>;
};
// conversion for the second group of standard containers
template <template <class...> class C, class IK, class IT, class OK, class OT>
struct convert_container<C<IK, IT>, std::pair<OK, OT>>
{
using type = C<OK, OT>;
};
// conversion for the third group of standard containers
template
<
template <class, std::size_t> class C, std::size_t N, class IT, class OT
>
struct convert_container<C<IT, N>, OT>
{
using type = C<OT, N>;
};
template <typename C, typename T>
using convert_container_t = typename convert_container<C, T>::type;
transform_container function implementation:
template
<
class InputContainer,
class Functor,
class InputType = typename InputContainer::value_type,
class OutputType = typename std::result_of<Functor(InputType)>::type,
class OutputContainer = convert_container_t<InputContainer, OutputType>
>
OutputContainer transform_container(const InputContainer& ic, Functor f)
{
OutputContainer oc;
std::transform(std::begin(ic), std::end(ic), std::inserter(oc, oc.end()), f);
return oc;
}
Example of use
See live example with the following conversions:
std::vector<int> -> std::vector<std::string>,
std::set<int> -> std::set<double>,
std::map<int, char> -> std::map<char, int>.
Problems
std::array<int, 3> -> std::array<double, 3> conversion doesn't compile because std::array haven't insert method which is needed due to std::inserter). transform_container function shouldn't also work for this reason with the following containers: std::forward_list, std::queue, std::priority_queue, std::stack, [std::valarray].
Doing this in general is going to be pretty hard.
First, consider std::vector<T, Allocator=std::allocator<T>>, and let's say your functor transforms T->U. Not only do we have to map the first type argument, but really we ought to use Allocator<T>::rebind<U> to get the second. This means we need to know the second argument is an allocator in the first place ... or we need some machinery to check it has a rebind member template and use it.
Next, consider std::array<T, N>. Here we need to know the second argument should be copied literally to our std::array<U, N>. Perhaps we can take non-type parameters without change, rebind type parameters which have a rebind member template, and replace literal T with U?
Now, std::map<Key, T, Compare=std::less<Key>, Allocator=std::allocator<std::pair<Key,T>>>. We should take Key without change, replace T with U, take Compare without change and rebind Allocator to std::allocator<std::pair<Key, U>>. That's a little more complicated.
So ... can you live without any of that flexibility? Are you happy to ignore associative containers and assume the default allocator is ok for your transformed output container?
The major difficulty is to somehow get the container type Container from Conainer<T>. I have shamelessly stolen the code from template metaprogramming: (trait for?) dissecting a specified template into types T<T2,T3 N,T4, ...>, in particular, Kerrek SB's answer (the accepted answer), as I am not familiar with template metaprogramming.
#include <algorithm>
#include <cassert>
#include <type_traits>
// stolen from Kerrek SB's answer
template <typename T, typename ...>
struct tmpl_rebind {
typedef T type;
};
template <template <typename ...> class Tmpl, typename ...T, typename ...Args>
struct tmpl_rebind<Tmpl<T...>, Args...> {
typedef Tmpl<Args...> type;
};
// end of stolen code
template <typename Container,
typename Func,
typename TargetType = typename std::result_of<Func(typename Container::value_type)>::type,
typename NewContainer = typename tmpl_rebind<Container, TargetType>::type >
NewContainer convert(const Container& c, Func f) {
NewContainer nc;
std::transform(std::begin(c), std::end(c), std::inserter(nc, std::end(nc)), f);
return nc;
}
int main() {
std::vector<int> vi{ 1, 2, 3, 4, 5 };
auto vs = convert(vi, [] (int i) { return std::to_string(i); });
assert( vs == std::vector<std::string>( {"1", "2", "3", "4", "5"} ) );
return 0;
}
I have tested this code with gcc 4.7.2 and clang 3.5 and works as expected.
As Yakk points out, there are quite a few caveats with this code though: "... should your rebind replace all arguments, or just the first one? Uncertain. Should it recursively replace T0 with T1 in later arguments? Ie std::map<T0, std::less<T0>> -> std::map<T1, std::less<T1>>?" I also see traps with the above code (e.g. how to deal with different allocators, see also Useless' answer).
Nevertheless, I believe the above code is already useful for simple use cases. If we were writing a utility function to be submitted to boost, then I would be more motivated to investigate these issues further. But there is already an accepted answer so I consider the case closed.
Many thanks to Constructor, dyp and Yakk for pointing out my mistakes / missed opportunities for improvements.
I wrote a blog post to solve a similar problem recently. Using templates and the iterator interface was the route I chose to follow.
for_each:
To cut down on the amount of boilerplate, we're going to create a using clause that allows us to grab the type contained within an iterator:
template <typename IteratorType>
using ItemType = typename std::iterator_traits<typename IteratorType::iterator>::value_type;
With that in place, we can implement a helper function for_each like so:
template <typename IteratorType>
void for_each(IteratorType &items, std::function<void(ItemType<IteratorType> const &item)> forEachCb)
{
for (typename IteratorType::iterator ptr = items.begin(); ptr != items.end(); ++ptr)
forEachCb(*ptr);
}
transform_container:
Finally transform_container, could be implemented like so:
template <typename IteratorType, typename ReturnType>
ReturnType transform_container(IteratorType &items, std::function<ItemType<ReturnType>(ItemType<IteratorType> const &item)> mapCb)
{
ReturnType mappedIterator;
for_each<IteratorType>(items, [&mappedIterator, &mapCb](auto &item) { mappedIterator.insert(mappedIterator.end(), mapCb(item)); });
return mappedIterator;
}
Which will allow us to call your two examples in the following way:
std::vector<int> vi{ 1, 2, 3, 4, 5 };
auto vs = transform_container<std::vector<int>, std::vector<std::string>>(vi, [](int i){return std::to_string(i);});
assert(vs == std::vector<std::string>({"1", "2", "3", "4", "5"}));
std::set<int> si{ 5, 10, 15 };
auto sd = transform_container<std::set<int>, std::set<double>>(si, [] (int i) { return i / 2.; });
assert(sd == std::set<double>({5/2., 10/2., 15/2.}));
My blog post also goes into a little more detail if that's helpful.
I'm trying to deduce the underlying template type T from a type E = T<T2,T3>. This would for example make it possible to make a template function pair_maker(const E & a) which can be used with one of several similar types of containers. Rough meta code:
template <typename T>
auto pairmaker(const E & a) -> PairContents<E,std::string>::type {
ContainerPairMaker<E,std::string>::type output;
... some code ...
return output;
}
PairContents<E,std::string>
would transform the type vector<int> into vector<pair(int,std::string)> or whatever<T1> into whatever<pair(T1,std::string)>.
Another similar example of type dissection is for std::array (or similar containers) where I like to figure out the container type to make a new similar array. For example for these kind of functions (this is actual working code now)
template <typename T >
auto make_some3(const T & a)
-> std::array<typename T::value_type,10*std::tuple_size<T>::value>{
return std::array<typename T::value_type,10*std::tuple_size<T>::value>{} ;
}
This works fine but what I'm after is to make the explicit use of 'std::array' automatic.
For std::array there's the tuple_size trait which helps, and a similar thing can be used to find the type for any second argument, but again I can't think of anything for finding the container type.
To summarize: what kind of machinery (if any) can be used for cases like these. To which extent is it possible to deal with mixes of template arguments, template-template arguments, any number of arguments, and non-template arguments of unknown types.
Here's an idea:
template <typename T, typename ...>
struct tmpl_rebind
{
typedef T type;
};
template <template <typename ...> class Tmpl, typename ...T, typename ...Args>
struct tmpl_rebind<Tmpl<T...>, Args...>
{
typedef Tmpl<Args...> type;
};
Usage:
typedef std::vector<int> IV;
typedef typename tmpl_rebind<IV, std::pair<double, std::string>>::type PV;
Now PV = std::vector<std::pair<double, std::string>>.
This is a self answer I came up with as a variant of the answer from Kerrek SB
It is possible to make a trait that extracts std::vector from std::vector<int> and exposes it as ::type via a trait. Yes, this solution is nearly identical to Kerrek's, but to me the use syntax is more aesthetic, putting the template parameters after ::type.
template <typename T, typename ...>
struct retemplate
{
typedef T type;
};
template <template <typename ...> class Tmpl, typename ...T>
struct retemplate<Tmpl<T...>>
{
template <typename ...AR>
using type=Tmpl<AR...> ;
};
with this you actually get retemplate<T<A,B,C>>::type equal to the template T
example use:
typedef std::vector<int> intvec;
typedef retemplate<intvec>::type<double> doublevec;
or to expose the container type
typedef std::vector<int> intv;
template <typename ...T>
using vector_T= retemplate<intv>::type<T...> ;
Note that when using this in template context, an extra template is required just after ::, like this: (elaborating on the comment from Xeo)
template <typename T>
typename retemplate<T>::template type<double> containedDouble(T& a) {
decltype(containedDouble(a)) out;
for (auto &i : a)
out.push_back(i);
return out;
}
What that does is to take an object of type T1<T2> and copy its content into a T1<double>. For example with T1==std::vector and T2==int.
I recommend taking a look at A. Alexandrescu's book Modern C++ Design.
If I recall correctly he explains how one might use type lists to store and access arbitrary types in a list-like fashion. These lists can be used to provide type information in a number of different situations. Take a look at the implementation of Loki to see how type lists can be utilized.
I'm not sure if this is helpful at all, but maybe you can learn something from the ideas used in Loki in order to solve or at least better understand your specific issues at hand.
Normally in templates you want to know the entire type, but in my case I need to know more, and want to "break up" the type. Take this example:
template <typename Collection<typename T> >
T get_front(Collection const& c)
{
return c.front();
}
How can I achieve that? Note: I need it to to automatically deduce the types, not pass in something like <std::vector<int>, int>
Edit: A C++0x way can be found at the end.
Edit 2: I'm stupid, and a way shorter C++98/03 way than all this traits stuff can be found at the end of the answer.
If you want your function to work for any arbitary standard library container, you need to pull out some Template Guns.
The thing is, that the different container take a different amount of template parameters. std::vector, std::deque and std::list for example take 2: the underlying item type T and the allocator type Alloc. std::set and std::map on the other hand take 3 and 4 respectively: both have the key type K, map takes another value type V, then both take a comparator Compare type and the allocator type Alloc. You can get an overview of all container types supplied by the standard library for example here.
Now, for the Template Guns. We will be using a partially specialized traits metastruct to get the underlying item type. (I use class instead of typename out of pure preference.)
template<class T>
struct ContainerTraits;
// vector, deque, list and even stack and queue (2 template parameters)
template<
template<class, class> class Container,
class T, class Other
>
struct ContainerTraits< Container<T,Other> >{
typedef T value_type;
};
// for set, multiset, and priority_queue (3 template parameters)
template<
template<class, class, class> class Container,
class T, class Other1, class Other2
>
struct ContainerTraits< Container<T,Other1,Other2> >{
typedef T value_type;
};
// for map and multimap (4 template parameters)
template<
template<class, class, class, class> class Container,
class Key, class T, class Other1, class Other2
>
struct ContainerTraits< Container<Key,T,Other1,Other2> >{
typedef Container<Key,T,Other1,Other2> ContainerT;
// and the map returns pair<const Key,T> from the begin() function
typedef typename ContainerT::value_type value_type;
};
Now that the preparation is done, on to the get_front function!
template<class Container>
typename ContainerTraits<Container>::value_type
get_front(Container const& c){
// begin() is the only shared access function
// to the first element for all standard container (except std::bitset)
return *c.begin();
}
Phew! And that's it! A full example can be seen on Ideone. Of course it would be possible to refine that even further, to the point of returning the actual value that is mapped to a key in a std::map, or use container specific access functions, but I was just a bit too lazy to do that. :P
Edit
A way easier C++0x way is using the new trailing-return-type function syntax, of which an example can be found here on Ideone.
Edit 2
Well, I don't know why, but I totally didn't think of the nested typedefs when writing this answer. I'll let the verbose way stay as a reference for traits classes / pattern matching a template. This is the way to do it, it is basically the same I did with the traits classes, but ultimately less verbose.
You could do this if you know it's not an associative container.
template <typename Collection>
Collection::type_name get_front(Collection const& c)
{
return c.front();
}
I'm assuming you want both Collection and T as template parameters. To do that simply type
template< template < typename > class Collection, typename T >
T get_front( Collection< T > const& c )
...
The construct template < typename > class Collection tells the compiler that Collection is a template itself with one parameter.
Edit:
As pointed out be Xeo, vector takes two template parameters, and your templates need to reflect that, i.e.
template< template < typename, typename > class Collection,
typename T, typename Alloc >
T get_front( Collection< T, Alloc > const& c )
...
Seeing as Collection<T> is known ahead of time, I think what you want is:
template <typename T>
T get_front(Collection<T> const& c)
{
return c.front();
}
The only part that's changing is what T is, it's always in a Collection (contents, not the container) so you don't have put that as part of the template.
If the container was changing, using c.front() could be dangerous. You would need to verify that the collection type had a method front that took no parameters and return a T.
Edit:
If you do need to template Collection, then that's more like:
template<typename C, typename T>
T get_front(C<T> const & c)
I would avoid something that generic if you can, perhaps specializing the function for collections you know will be used, or to a particular class of classes (if possible).