Template deduction fails for std:map as template parameter - templates

I'm implementing a simple LRU cache in C++11. I pretty much have it covered but there's just one minor problem. Let's say I have the following template class definition:
#ifndef _LRU_STL_H_
#define _LRU_STL_H_
#include <functional>
#include <cassert>
#include <list>
template <typename KeyType, typename ValueType, template<typename...> class Map>
class LRU {
public:
typedef Map<KeyType, std::pair<ValueType, typename std::list<KeyType>::iterator>> KeyToValueType;
LRU(const std::function<ValueType(const KeyType&)> &Function, size_t Capacity)
: _Function(Function), _Capacity(Capacity) {
assert(_Capacity != 0);
}
...
private:
...
std::function<ValueType(const KeyType&)> _Function;
const size_t _Capacity;
KeyToValueType _KeyToValue;
};
#endif
At KeyToValue type I get the following compilation error with MSVC2013: Error 1 error C2976: 'std::map' : too few template arguments c:\x\visual studio 2013\projects\caching\lru_stl\lru_stl.h 17 1 LRU_STL
The 17th line is:
typedef Map<KeyType, std::pair<ValueType, typename std::list<KeyType>::iterator>> KeyToValueType;
Seems like the template deduction fails. It may be a very simple problem but I just couldn't find it yet. For completeness here's an example:
std::function<std::string(std::string)> functionToCache = [](std::string & str) {
std::string reverse;
reverse.reserve(str.size());
std::copy(str.begin(), str.end(), reverse);
return reverse;
};
LRU<std::string, std::string, std::map> LRU(functionToCache, 5);
std::string Hello_World = LRU("Hello World");
assert(Hello_World == "dlroW olleH");
The error is already provided. Done mentioned fixes. Still the same error occurs: std::map too few template arguments.
Just for completeness if I remove everything and create a TEST class:
template <typename A, typename B, template <typename ...> class Map>
class TEST {
typename Map<A, std::pair<B, typename std::list<A>::iterator>> CMAP;
public:
TEST(void) { }
};
Trying to instantiate the class results in the exact same error message.
#Update:
VC++ Compiler seems to be unable to process default template parameters in this particular scenario. To solve the issue I had to add all four template parameters to the typedef and so the definition became like:
template <typename K, typename V, template <typename...> class Map>
class Test {
typedef Map<K, std::pair<V, typename std::list<K>::iterator>, std::less<K>, std::allocator<std::pair<const K, typename std::list<K>::iterator>>> MapType;
};
That would be all to this issue. Thanks for all who tried to help and for that professional gentleman with: 'I don't have even the slightest idea about this question, Let's DOWNVOTE it!!!'. You really are amazing! Wish you the best man....

Your missed two points.
First, template template parameter should be like this:
template < parameter-list > class name
So your
template<typename...> Map
should be
template<typename...> class Map
Second, you should use typename with dependent names. In your code, std::list<Key>::iterator is a dependent name (depending on Key). So, you should use typename std::list<Key>::iterator instead.
Here's my corrected test code.
#include <list>
#include <map>
template <typename Key, typename Value,
template <typename...> class Map>
class Test
{
public:
typedef Map<
Key,
std::pair<Value, typename std::list<Key>::iterator>
> KeyToValueType;
};
int main()
{
Test<int, char, std::map>::KeyToValueType asdf;
}
It worked both in g++ 4.9.1 and in clang++ 3.5.
It seems to be due to VC++'s foolishness. It may work if you give the full template parameter to std::map, including comparer and allocator, since VC++ seems not to be able to process default template parameter in this case.

Related

Template class with iterator to a STL container

I want to create a template class which has an iterator of a STL container as a member. That is how far I got:
#include <iostream>
#include <vector>
using namespace std;
template<typename Element, template <class> class StdLibContainer>
struct ClassHoldingAnIteratorToAStandardContainer
{
ClassHoldingAnIteratorToAStandardContainer(){}
typename StdLibContainer<Element*>::iterator std_lib_iterator;
};
int main()
{
vector<int> vec{1,2,3};
ClassHoldingAnIteratorToAStandardContainer<int,vector<int>> holding_iterator_to_vec;
//DOES NOT WORK, compiler says: expected a class template, got ‘std::vector<int>’
return 0;
}
Could you explain the syntax template <typename> class StdLibContainer?
I found it somewhere on stackoverflow. BUt I don't understand it.
How can I create an instance of ClassHoldingAnIteratorToAStandardContainer ? All my attempts failed so far. The compiler always gives the error message: `expected a class template, got ‘std::vector’
In the above example i want to assign holding_iterator_to_vec vec.begin().
template <typename> class is the same as template <class> class. Originally, when templates were introduced, they allowed two equivalent forms:
template<class T> struct Foo {};
// or
template<typename T> struct Foo {};
Do not ask me why! However, the same was not true for template template parameters:
template <template <class> typename T> struct Foo {};
was the only allowed syntax. Apparently, people were unhappy about it, so the syntax was relaxed.
As for your second question, std::vector takes at least two template arguments, data type and allocator. This is why a single argument template doesn't cut it before C++17. After C++17, it would work.
To make it universal, use
template<template <class...> class Container> struct Foo{};
Unless you really need to know the type of the container, I would strongly recommend to keep your ClassHoldingAnIteratorToAStandardContainer independent of the concrete container type. If you just need the iterator, this is simpler and sufficient:
template<typename iterator>
struct iterator_wrapper {
iterator iter;
};
Thats the minimum you need to have an iterator as member :).
I dont really know what you want to use the iterator for, so just for the sake of an example lets add methods that actually use the iterator....
#include <iterator>
#include <vector>
#include <iostream>
template<typename iterator>
struct iterator_wrapper {
using value_type = typename std::iterator_traits<iterator>::value_type;
iterator iter;
bool operator!=(const iterator& other) { return iter != other;}
iterator_wrapper& operator++(){
++iter;
return *this;
}
const value_type& operator*() { return *iter; }
};
template <typename iterator>
iterator_wrapper<iterator> wrap_iterator(iterator it) {
return {it};
}
int main() {
std::vector<int> vec{1,2,3};
auto it = wrap_iterator(vec.begin());
for (;it != vec.end();++it) std::cout << *it;
}
Also there is a problem in your code.
typename StdLibContainer<Element*>::iterator
is for containers of pointers while in main you have ints. If you want to infer the iterator type from the container type then you can do it for example like this:
template <typename container,
typename iterator = typename container::iterator>
iterator_wrapper<iterator> wrap_begin(container& c) {
return {c.begin()};
}
which makes creating an iterator_wrapper as simple as
auto x = wrap_begin(vec);
Note that this answer applies to C++11, in newer standards there are deduction guides that make such make_x methods more or less superfluous afaik.

Template Meta-programming, with Variadic Templates: compiler error

I'm attempting variadic template meta programming for the first time and consistently getting a compiler error that I have not been able to track down.
I'm following the "tuple" example on this page (although I'm calling my object an ItemSet)
The ItemSet part compiles just fine:
template<typename...Ts> class ItemSet { };
template<typename item_T, typename ...Ts>
class ItemSet<item_T, Ts...> : public ItemSet<Ts...>
{
public:
ItemSet(item_T t, Ts... item_types) : ItemSet<Ts...>(item_types...), tail(t) { }
protected:
item_T tail;
};
template <typename...M> struct type_holder;
template<typename T, typename ...M>
struct type_holder<0, ItemSet<T, M...>>
{ // ERROR: M parameter pack expects a type template argument.
typedef T type;
};
template <int k, typename T, typename ...M>
struct type_holder<k, ItemSet<T, M...>>
{
typedef typename type_holder<k - 1, ItemSet<M...>>::type type;
};
int main()
{
ItemSet<int, string, string, double> person(0, "Aaron", "Belenky", 29.492);
}
However, in the commented out code, I get the compiler error at the declaration of type_holder.
I've tried a bunch of variations on the same syntax, but always with the same issue.
I'm using Microsoft Visual Studio 2013, which is supposed to have full support for Template programming and Variadic Templates.
Do you understand what the compiler error is, and can you explain it to me?
The immediate problem is that you are defining a specialization of type_holder without a general template. In addition, there is a simple typo (typeholder instead of type_holder). Fixing these two issues makes it compile with other compilers:
template <int, typename T>
struct type_holder;
template <int k, typename T, typename ...M>
struct type_holder<k, ItemSet<T, M...>>
{
typedef typename type_holder<k - 1, ItemSet<M...>>::type type;
};
template<class T, class ...M>
struct type_holder<0, ItemSet<T, M...>>
{
typedef T type;
};
The error emitted by the compilers you used isn't particular helpful. I'd recommend keeping a few C++ compilers around just to test template code with (I'm typically using gcc, clang, and Intel's compiler).

Check if type is hashable

I would like to make a type trait for checking if a particular type is hashable using the default instantiations of the standard library's unordered containers, thus if it has a valid specialization for std::hash. I think this would be a very useful feature (e.g. for using std::set as failsafe for std::unordered_set in generic code). So I, thinking std::hash is not defined for each type, started making the following SFINAE solution:
template<typename T> std::true_type hashable_helper(
const T&, const typename std::hash<T>::argument_type* = nullptr);
template<typename T> std::false_type hashable_helper(...);
//It won't let me derive from decltype directly, why?
template<typename T> struct is_hashable
: std::is_same<decltype(hashable_helper<T>(std::declval<T>())),
std::true_type> {};
(Forgive my modest SFINAE-abilities if this is not the best solution or even wrong.)
But then I learned, that both gcc 4.7 and VC++ 2012 define std::hash for any type T, just static_asserting in the non-specialized version. But instead of compiling conditionally they (and also clang 3.1 using gcc 4.7's libstdc++) fail the assertion resulting in a compile error. This seems reasonable since I think static_asserts are not handled by SFINAE (right?), so an SFINAE solution seems not possibly at all. It's even worse for gcc 4.6 which doesn't even have a static_assert in the general std::hash template but just doesn't define its () operator, resulting in a linker error when trying to use it (which is always worse than a compile error and I cannot imagine any way to transform a linker error into a compiler error).
So is there any standard-conformant and portable way to define such a type trait returning if a type has a valid std::hash specialization, or maybe at least for the libraries static_asserting in the general template (somehow transforming the static_assert error into a SFINAE non-error)?
Since C++17 it is now possible to do this in a more elegant way.
From cppreference about std::hash:
Each specialization of this template is either enabled ("untainted") or disabled ("poisoned"). For every type Key for which neither the library nor the user provides an enabled specialization std::hash, that specialization exists and is disabled. Disabled specializations do not satisfy Hash, do not satisfy FunctionObject, and std::is_default_constructible_v, std::is_copy_constructible_v, std::is_move_constructible_v, std::is_copy_assignable_v, std::is_move_assignable_v are all false. In other words, they exist, but cannot be used.
This meant that the STL had to remove the static_assert in C++17. Here is a working solution with 'Clang-6.0.0 -std=c++17':
#include <functional>
#include <ios>
#include <iostream>
#include <type_traits>
template <typename T, typename = std::void_t<>>
struct is_std_hashable : std::false_type { };
template <typename T>
struct is_std_hashable<T, std::void_t<decltype(std::declval<std::hash<T>>()(std::declval<T>()))>> : std::true_type { };
template <typename T>
constexpr bool is_std_hashable_v = is_std_hashable<T>::value;
struct NotHashable {};
int main()
{
std::cout << std::boolalpha;
std::cout << is_std_hashable_v<int> << std::endl;
std::cout << is_std_hashable_v<NotHashable> << std::endl;
return 0;
}
This might for example come in handy when you use boost::hash_combine or boost::hash_range. If you include a header containing the following code sample you do not need to define boost hashes for specific types anymore.
#include <boost/functional/hash_fwd.hpp>
template <typename T, typename = std::void_t<>>
struct is_boost_hashable : std::false_type { };
template <typename T>
struct is_boost_hashable<T, std::void_t<decltype(boost::hash_value(std::declval<T>()))>> : std::true_type { };
template <typename T>
constexpr bool is_boost_hashable_v = is_boost_hashable<T>::value;
namespace boost
{
template <typename T>
auto hash_value(const T &arg) -> std::enable_if_t<is_std_hashable_v<T> &&
!is_boost_hashable_v<T>, std::size_t>
{
return std::hash<T>{}(arg);
}
}
Notice the is_boost_hashable_v, this is necessary to avoid ambiguity as boost already provides hashes for a lot of hashes.
It seems we have two conflicting requirements:
SFINAE is meant to avoid any instantiation of a template if the instantiation might fail and remove the corresponding function from the overload set.
static_assert() is meant to create an error, e.g., during instantiation of a template.
To my mind, 1. clearly trumps 2., i.e., your SFINAE should work. From the looks of two separate compiler vendors disagree, unfortunately not between themselves but with me. The standard doesn't seem to specify how the default definition of std::hash<T> looks like and seems to impose constraints only for the cases where std::hash<T> is specialized for a type T.
I think your proposed type traits is a reasonable idea and it should be supported. However, it seems the standard doesn't guarantee that it can be implemented. It may be worth bringing this up with the compiler vendors and/or filing a defect report for the standard: The current specification doesn't give clear guidance what should happen, as far as I can tell. ... and if the specification currently mandates that a type traits as above fails it may be a design error which needs to be corrected.
Here is a VERY dirty solution to your problem: It works for GCC 4.7 (and not 4.6, due to missing C++11 feature: mangling overload)
// is_hashable.h
namespace std {
template <class T>
struct hash {
typedef int not_hashable;
};
}
#define hash hash_
#define _Hash_impl _Hash_impl_
#include<functional>
#undef hash
#undef _Hash_impl
namespace std {
struct _Hash_impl: public std::_Hash_impl_{
template <typename... Args>
static auto hash(Args&&... args)
-> decltype(hash_(std::forward<Args>(args)...)) {
return hash_(std::forward<Args>(args)...);
}
};
template<> struct hash<bool>: public hash_<bool> {};
// do this exhaustively for all the hashed standard types listed in:
// http://en.cppreference.com/w/cpp/utility/hash
}
template <typename T>
class is_hashable
{
typedef char one;
typedef long two;
template <typename C> static one test( typename std::hash<C>::not_hashable ) ;
template <typename C> static two test(...);
public:
enum { value = sizeof(test<T>(0)) == sizeof(long) };
};
// main.cpp
// #include "is_hashable.h"
#include<iostream>
#include<unordered_set>
class C {};
class D {
public:
bool operator== (const D & other) const {return true;}
};
namespace std {
template <> struct hash<D> {
size_t operator()(const D & d) const { return 0;}
};
}
int main() {
std::unordered_set<bool> boolset;
boolset.insert(true);
std::unordered_set<D> dset;
dset.insert(D());// so the hash table functions
std::cout<<is_hashable<bool>::value<<", ";
std::cout<<is_hashable<C>::value << ", ";
std::cout<<is_hashable<D>::value << "\n";
}
And the output is:
1, 0, 1
We basically "hijack" the hash symbol and inject some helper typedef in it. You'll need to modify it for VC++, in particular, the fix for _Hash_impl::hash() since it's an implementation detail.
If you make sure that the section labelled as is_hashable.h is included as the first include this dirty trick should work...
I hit this too. I tried a few workarounds and went with a whitelist filter for std::hash<>. the whitelist is not pleasant to maintain, but it is safe and it works.
I tried this on VS 2013, 2015, clang and gcc.
#include <iostream>
#include <type_traits>
// based on Walter Brown's void_t proposal
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3911.pdf
namespace detail {
template<class... TN> struct void_t {typedef void type;};
}
template<class... TN>
struct void_t {typedef typename detail::void_t<TN...>::type type;};
// extensible whitelist for std::hash<>
template <class T, typename = void>
struct filtered_hash;
template <class T>
struct filtered_hash<T,
typename std::enable_if<std::is_enum<T>::value>::type>
: std::hash<T> {
};
template <class T>
struct filtered_hash<T,
typename std::enable_if<std::is_integral<T>::value>::type>
: std::hash<T> {
};
template <class T>
struct filtered_hash<T,
typename std::enable_if<std::is_pointer<T>::value>::type>
: std::hash<T> {
};
template<typename, typename = void>
struct is_hashable
: std::false_type {};
template<typename T>
struct is_hashable<T,
typename void_t<
typename filtered_hash<T>::result_type,
typename filtered_hash<T>::argument_type,
typename std::result_of<filtered_hash<T>(T)>::type>::type>
: std::true_type {};
// try it out..
struct NotHashable {};
static_assert(is_hashable<int>::value, "int not hashable?!");
static_assert(!is_hashable<NotHashable>::value, "NotHashable hashable?!");
int main()
{
std::cout << "Hello, world!\n";
}

Default values in templates with template arguments ( C++ )

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" )?
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.
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.
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:
....
};
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 { ... };
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.
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
It works with any number of default template parameters -- for instance, consider the case with std::map in the example.
There is no need to explicitly specify any default template parameters whatsoever.
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.

Templates and STL

The following code represents a container based on std::vector
template <typename Item>
struct TList
{
typedef std::vector <Item> Type;
};
template <typename Item>
class List
{
private
typename TList <Item>::Type items;
....
}
int main()
{
List <Object> list;
}
Is it possible to templatize std::vector and create a general container, something like that?
template <typename Item, typename stl_container>
struct TList
{
typedef stl_container<Item>;
};
where stl_container represents std::vector, std::list, std::set...? I would like to choose the type of container at the time of the creation.
List <Object, std::vector> list; //vector of objects, not a real code
List <Object, std::vector> list; //list of objects, not a real code
Thanks for your answers...
Updated question:
I tried the following code but there are errors:
#include <vector>
template <typename Item, typename Container>
struct TList
{
typedef typename Container <Item>::type type; //Error C2059: syntax error : '<', Error C2238: unexpected token(s) preceding ';
};
template <typename T>
struct vector_container
{
typedef std::vector<T> type;
};
int _tmain(int argc, _TCHAR* argv[])
{
TList <int, vector_container> v;
TList <int, map_container> m;
}
Yes, but not directly:
template <typename Item, template <typename> class Container>
struct TList
{
typedef typename Container<Item>::type type;
};
Then you can define different container policies:
template <typename T>
struct vector_container
{
typedef std::vector<T> type;
};
template <typename T>
struct map_container
{
typedef std::map<T, std::string> type;
};
TList<int, vector_container> v;
TList<int, map_container> m;
A bit verbose, though.* To do things directly, you'd need to take the route described by James, but as he notes this is ultimately very inflexible.
However, with C++0x we can do this just fine:
#include <map>
#include <vector>
template <typename Item,
template <typename...> class Container, typename... Args>
struct TList
{
// Args lets the user specify additional explicit template arguments
Container<Item, Args...> storage;
};
int main()
{
TList<int, std::vector> v;
TList<int, std::map, float> m;
}
Perfect. Unfortunately there's no way to reproduce this in C++03, except via the indirection policy classes introduce as described above.
*I want to emphasize that by "A bit verbose" I mean "this is unorthodox". The correct solution for your problem is what the standard library does, as Jerry explains. You just let the user of your container adapter specify the entire container type directly:
template <typename Item, typename Container = std::vector<Item>>
struct TList
{};
But this leaves a big problem: what if I don't want the value type of the container to be Item but something_else<Item>? In other words, how can I change the value type of an existing container to something else? In your case you don't, so read no further, but in the case we do, we want to rebind a container.
Unfortunately for us, the containers don't have this functionality, though allocators do:
template <typename T>
struct allocator
{
template <typename U>
struct rebind
{
typedef allocator<U> type;
};
// ...
};
This allows us to get an allocator<U> given an allocator<T>. How can we do the same for containers without this intrusive utility? In C++0x, it's easy:
template <typename T, typename Container>
struct rebind; // not defined
template <typename T, typename Container, typename... Args>
struct rebind<T, Container<Args...>>
{
// assumes the rest are filled with defaults**
typedef Container<T> type;
};
Given std::vector<int>, we can perform rebind<float, std::vector<int>>::type, for example. Unlike the previous C++0x solution, this one can be emulated in C++03 with macros and iteration..
**Note this mechanism can be made much more powerful, like specifying which arguments to keep, which to rebind, which to rebind themselves before using as arguments, etc., but that's left as an exercise for the reader. :)
I'm a bit puzzled why some very smart (and competent) people are saying no.
Unless I've misread your question, what you're trying to accomplish is virtually identical to the "container adapters" in the standard library. Each provides an interface to some underlying container type, with the container type that will be used provided as a template parameter (with a default value).
For example, a std::stack uses some other container (e.g., std::deque, std::list or std::vector) to hold the objects, and std::stack itself just provides a simplified/restricted interface for when you just want to use stack operations. The underlying container that will be used by the std::stack is provided as a template parameter. Here's how the code looks in the standard:
namespace std {
template <class T, class Container = deque<T> >
class stack {
public:
typedef typename Container::value_type value_type;
typedef typename Container::size_type size_type;
typedef Container container_type;
protected:
Container c;
public:
explicit stack(const Container& = Container());
bool empty() const { return c.empty(); }
size_type size() const { return c.size(); }
value_type& top() { return c.back(); }
const value_type& top() const { return c.back(); }
void push(const value_type& x) { c.push_back(x); }
void pop() { c.pop_back(); }
};
}
Of course, perhaps I've just misunderstood the question -- if so, I apologize in advance to the people with whom I'm (sort of) disagreeing.
Yes and no.
You can use a template template parameter, e.g.,
template <typename Item, template <typename> class Container>
struct TList { /* ... */ };
However, in general, template template parameters are not particularly useful because the number and types of the template parameters have to match. So, the above would not match std::vector because it actually has two template parameters: one for the value type and one for the allocator. A template template parameter can't take advantage of any default template arguments.
To be able to use the std::vector template as an argument, TList would have to be declared as:
template <typename Item, template <typename, typename> class Container>
struct TList { /* ... */ };
However, with this template, you wouldn't be able to use the std::map template as an argument because it has four template parameters: the key and value types, the allocator type, and the comparator type.
Usually it is much easier to avoid template template parameters because of this inflexibility.
well, you can hack it up with a macro:
template <typename T, typename stl_container = std::vector<T> >
struct TList
{
typedef stl_container Type;
};
#define TLIST(T, C) TList<T, C<T> >
TList<int> foo;
TList<int, std::list<int> > bar;
TLIST(int, std::list) baz;
Is it possible to templatize std::vector and create a general container, something like that?
No. You would have to templatize the function or object using the container -- you couldn't templatize the container itself.
For example. consider a typical std::find:
template<class InputIterator, class T>
InputIterator find ( InputIterator first, InputIterator last, const T& value )
{
for ( ;first!=last; first++) if ( *first==value ) break;
return first;
}
This works for any container, but doesn't need a tempalte with the container at all.
Also, given that it looks what you're trying to do is make container independent code, you might want to buy or borrow yourself a copy of Scott Meyers' Effective STL and read Item 2: Beware the illusion of container-independent code.
You can use template template parameters as others have mentioned here. The main difficulty with this is not that dissimilar container types have dissimilar template parameters, but that the standard allows the standard containers, like vector, to have template parameters in addition to the documented, necessary ones.
You can get around this by providing your own subclass types that accept the appropriate template parameters and let any extras (which have to have defaults) be filled in my the implementation:
template < typename T > struct simple_vector : std::vector<T> {};
Or you can use the templated typedef idiom:
template < typename T > struct retrieve_vector { typedef std::vector<T> type; };