Pass a std container to a function - c++

I came up with the following:
template <typename T> inline void printcontainer( std::vector<T> container )
{
for( auto it = container.begin(); it != container.end(); it++ )
{
std::cout << *it << std::endl;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
std::vector<int> v;
v.push_back(5);
v.push_back(4);
v.push_back(3);
printcontainer(v);
return 0;
}
(Sorry for the push_backs, visual studio doesn't accept initializer lists...ugh!!)
now this function is limited to std::vector, how can I make it so that I can pass other containers, like std::list arrays etc...

Simply don't template on the type stored by the container, but on the type of the container itself:
template <typename Container>
inline void printcontainer(const Container &container)
Note that I changed the argument to const reference to avoid an unnecessary copy.
You can generalize your print function to C arrays by using the non-member std::begin and std::end or by using a range based for loop:
template <typename Container>
inline void printcontainer(const Container &container) {
for (const auto &v : container)
std::cout << v << "\n";
}
OT remark: You probably do not need the inline here.

Passing container objects around is against classic Stepanov's STL container-iterator-algorithm Generic Programming style.
Usually one would pass iterators:
# define ForwardIterator typename // workaround untill we have concepts
template <ForwardIterator It> inline void printcontainer( It begin, It end )
{
for(;begin != end; ++begin)
{
std::cout << *begin << std::endl;
}
}
Usage:
std::vector<int> v = {1, 2, 3, 4};
printcontainer(v.cbegin(), v.cend());

Related

How to realize template class type in template function?

I want to design a print function for STL container, include: std::vector, std::map, std::unodered_map, std::set, std::unordered_set, std::list ....
The ideal function looks like:
template<typename T>
void Show(const T& t)
{
if (istype(t, std::vector))
{
// here is a presudo code
// print vector
for (auto i: t) cout << i << endl;
}
else if (istype(t, std::unordered_map))
{
// print unordered_map
}
else
{ }
}
I think the problem happens on istype()
I know there is some function like std::is_same can do this.
But it seems it just can operate on int or float, cant be operated on std::vector, can you help on this?
But it seems it just can operate on int or float, cant be operated
on std::vector, can you help on this?
For template classes like stranded containers, you need to specify the template argument to get the concrete type and thereafter you can compare usingstd::is_same. That means something like std::is_same_v<T, std::vector<int>> or std::is_same_v<T, std::vector<float>>, etc... will work.
On the other side, you need to see whether the passed container is a specialization for the standard container. There you need your own std::is_same_v like type traits.
One possible implementation will look like as follows. Also note that you need to use the if constexpr (since c++17) instead of normal if for compile time branching. If no access to c++17, you need to use SFINAE.
(See a Demo)
#include <iostream>
#include <type_traits> // std::false_type, std::true_type
#include <vector>
#include <list>
#include <string>
#include <map>
template<typename Type, template<typename...> class Args>
struct is_specialization final : std::false_type {};
template<template<typename...> class Type, typename... Args>
struct is_specialization<Type<Args...>, Type> : std::true_type {};
template<typename ContainerType>
constexpr void show(const ContainerType& container) noexcept
{
if constexpr (is_specialization<ContainerType, std::vector>::value
|| is_specialization<ContainerType, std::list>::value)
{
for (const auto ele : container)
std::cout << ele << '\t';
std::cout << '\n';
}
else if constexpr (is_specialization<ContainerType, std::map>::value)
{
for (const auto& [key, value]: container)
std::cout << key << " " << value << '\t';
std::cout << '\n';
}
// ... so on!
}
int main()
{
std::vector<int> vec{ 1, 2, 3 };
show(vec);
std::list<int> list{ 11, 22, 33 };
show(list);
std::map<int, std::string> mp{ {1, "string1"}, {2, "string2"}, {3, "string3"} };
show(mp);
}
Common feature of all STL containers is that they are iterable in range [begin(),end()). For sequence containers you have to handle printing T as value_type, for (Unordered) associative containers printing std::pair<const Key, T> as value_type must be handled. That is all. I don't see any reasons to checking type of passed container in your implemention.
template<class T>
void Print(const T& val) {
std::cout << val;
}
template<class Key, class Value>
void Print(const std::pair<const Key,Value>& p) {
Print(p.first);
std::cout << " - ";
Print(p.second);
}
template<class Cont>
void PrintCont(const Cont& cont) {
std::cout << std::endl;
for (auto it = cont.begin(); it != cont.end(); ++it) {
Print(*it);
std::cout << " ";
}
std::cout << std::endl;
}
std::array<int,2> a{1,2};
std::vector<int> v{1,2,3};
std::list<double> l{2., 3., 5.4};
std::map<int,double> m{ {1,2.},{10,3.14} };
std::multimap<int,int> m2{ {2,3},{4,5} };
std::set<float> s{1.f, 23.f, 2.f};
std::unordered_map<int,Foo> m3{ {1,Foo(1)}, {2,Foo(2)}, {3,Foo(3)}};
PrintCont(a);
PrintCont(v);
PrintCont(l);
PrintCont(m);
PrintCont(a);
PrintCont(s);
PrintCont(m2);
PrintCont(m3);
Live demo

STL container with a specific type as a generic argument

Is there any way that I can make a function which takes a container with a specific type (lets say std::string) as a parameter
void foo(const std::container<std::string> &cont)
{
for(std::string val: cont) {
std::cout << val << std::endl;
}
}
and call it for every type of stl container as input? like above?
std::set<std::string> strset;
std::vector<std::string> strvec;
std::list<std::string> strlist;
foo(strset);
foo(strvec);
foo(strlist);
You can make foo a function template taking a template template parameter for the container type.
e.g.
template<template<typename...> typename C>
void foo(const C<std::string> &cont)
{
for(std::string val: cont) {
std::cout << val << std::endl;
}
}
LIVE
Depending on if you want to overload foo for other cases or not
// Doesn't participate in overload resolution when not applicable
template<typename Container, typename = std::enable_if_t<std::is_same_v<typename Container::value_type, std::string>>>
void foo(const Container &cont) {
for(std::string val: cont) {
std::cout << val << std::endl;
}
}
// simpler
template<typename Container>
void foo(const Container &cont) {
static_assert(std::is_same_v<typename Container::value_type, std::string>, "Container must contain std::string")
for(std::string val: cont) {
std::cout << val << std::endl;
}
}
You might use a different test to std::is_same, such as std::is_convertible to allow
std::vector<char *> c_strings;
foo(c_strings);
You may want to consider using iterators instead. An intermediate result may look like
template<typename Iter>
void foo(Iter begin, Iter end) {
using T = decltype(*begin);
std::for_each(begin, end, [] (cons T & t) {
std::out << t << '\n';
}
}
Now using a callable template:
template<typename Iter, typename Callable>
void foo(Iter begin, Iter end, Callable & c) {
std::for_each(begin, end, c);
}
We just learned to use what the STL already offers.
Adding on to #songyuanyao's answer, I think we can generalize it further to:
template<template<typename...> typename C, typename ... D>
void foo(const C<D...> &cont)
{
for(const auto& val: cont) {
std::cout << val << std::endl;
}
}

Generic loop that iterates on both stl map and list (c++)

Is there any way to write a generic loop whcih iterates over values of both say stl map (associative container) and list (non associateve container).
template<typename T>
void foo(T &t)
{
for (auto iter = t.begin(); iter != t.end(); ++iter)
{
printf("%d\n", *iter); // will work for std::list<int> but not for std::map<int, int>
}
}
Thanks
To make it work for std::map - use proper adapter from boost:
foo(someMap | boost::adaptors::map_values);
You might also use Eric Niebler's ranges-v3
foo(someMap | ranges::values);
If you cannot use boost/ranges - use some kind of traits:
template <typename ValueType>
struct ValueGetter
{
static Value& get(ValueType& value)
{
return value;
}
};
template <typename Key, typename Value>
struct ValueGetter<std::pair<const Key, Value>>
{
using ValueType = std::pair<const Key, Value>;
static Value& get(ValueType& value)
{
return value.second;
}
};
template <typename ValueType>
auto& getValue(ValueType& value)
{
return ValueGetter<Value>::get(value);
}
template<typename T>
void foo(T &t)
{
for (auto iter = t.begin(); iter != t.end(); ++iter)
{
printf("%d\n", getValue(*iter));
}
}
Actually, there is already std::for_each (#include <algorithm>) for such purposes. You could feed it with appropriate handler, e. g. in the form of a lambda:
std::vector<int> v;
std::map<int, double> m;
std::for_each(v.begin(), v.end(), [](auto i) { printf("%d\n", i); });
std::for_each(m.begin(), m.end(), [](auto const& i) { printf("%d %f\n", i.first, i.second); });
I do like the way how Aconcagua show it and in most cases, this would be my favorite choice.
To make it more clear and fill the gap about my comment. The author's loop posted here is ok. In this case, the only problematic thing was about the printf(). To solve it, I have suggested something like overloading stream operator() to be able to print out std::map
ostream& operator<<(ostream& os, pair<string, int> it){
os << it.first << " => " << it.second;
}
as you might have realized the iterator in case of the std::map is the pair. Notice, you do not need to specify that for the std::list and the generic loop might look like the following
template<typename T>
void foo(T &t){
for(auto it=t.begin(); it=t.end(); ++it){
cout << *it << endl;
}
}
which is just fine with what was in the question, except printf() => cout. Here, you can also use range-based loops
template<typename T>
void foo(T &t){
for(auto it : t)
cout << it << endl;
}
and finally for_each() on the side with functor or lambda expression.

How to declare an iterator variable for unknown container

How to declare an iterator for unknown STL container? for example, I want to write a function that recieve container and print it all using iterators:
template <class Container> void print(Container c) {
// how to declare iterator???????
my_iterator = c.begin();
while(my_iterator!=c.end()) {
cout << *my_iterator << endl;
my_iterator++;
}
}
In C++03, you would need to get the iterator type from the container type explicitly:
typename Container::iterator it;
typename Container::const_iterator cit;
In C++11, you can just use auto:
auto my_iterator = c.begin(); // iterator in this case
auto my_iterator = c.cbegin(); // const_iterator always
Also note, as suggested my #Matthieu, that you can use a range based for loop in C++11, to simplify the code:
template <class Container>
void print(const Container& c)
{
for (const auto& elem : c)
cout << c << endl;
}
Go for:
for (auto& var : container)
{
cout << var << endl;
}
To display each elements of your container (and of any other type of container, even string or vector or map , ...)

C++ template specialization for map-like types

I want to define a generic function for printing contents of std::map like types. My initial attempt is a function like this:
template <class K, class V>
inline void PrintCollection(const std::map<K,V>& map,
const char* separator="\n",
const char* arrow="->",
const char* optcstr="") {
typedef typename std::map<K,V>::const_iterator iter_type;
std::cout << optcstr;
for (iter_type begin = map.begin(), it = begin, end = map.end();
it != end; ++it) {
if (it != begin) {
std::cout << separator;
}
std::cout << it->first << arrow << it->second;
}
std::cout << std::endl;
}
which works fine. When I try to generalize this function one more step, i.e. make it work for std::multimap type, compiler becomes angry. I tried several ways to make std::map generic in the function definition, such as:
template <class M, class K, class V>
inline void PrintCollection(const M<K,V>& map,
const char* separator="\n",
const char* arrow="->",
const char* optcstr="") {
typedef typename M<K,V>::const_iterator iter_type;
std::cout << optcstr;
for (iter_type begin = map.begin(), it = begin, end = map.end();
it != end; ++it) {
if (it != begin) {
std::cout << separator;
}
std::cout << it->first << arrow << it->second;
}
std::cout << std::endl;
}
with no success.
How can I generalize this function as I defined above?
To be more clear, I have already a function defined for vector-like classes defined before this function. It is like
template <class T>
inline void PrintCollection(const T& collection,
const char* separator="\n",
const char* optcstr="") {
typedef typename T::const_iterator iter_type;
std::cout << optcstr;
for (iter_type begin = collection.begin(), it = begin, end = collection.end();
it != end;
++it) {
if (it != begin) {
std::cout << separator;
}
std::cout << *it;
}
std::cout << std::endl;
}
So what I want to achieve it to make this function specialized to map-like classes. I'm pretty new in C++, so I don't know the exact term for this kind of stuff. Is this called "template specialization"?
Do it like the stdlib does and use iterators in your algorithm interfaces. This is the most generic solution.
template<class Iter>
void PrintCollection(Iter first, Iter last,
const char* separator="\n",
const char* arrow="->",
const char* optcstr="")
{
typedef Iter iter_type;
std::cout << optcstr;
for (iter_type begin = first, it = begin, end = last;
it != end; ++it) {
if (it != begin) {
std::cout << separator;
}
std::cout << it->first << arrow << it->second;
}
std::cout << std::endl;
}
int main()
{
vector<pair<int, int>> collection;
map<int, int> collection2;
pair<int, int> collection3[3];
PrintCollection(begin(collection), end(collection));
PrintCollection(begin(collection2), end(collection2));
PrintCollection(begin(collection3), end(collection3));
}
The answer is fairly simple.
There is no dependency on typenames K and V in the function. So remove them and make a general template. It can be used for both map and multimap:
template <class AnyMap>
void PrintCollection(const AnyMap& map,
...
{
typedef typename AnyMap::const_iterator iter_type;
On side note, with templates, you don't need inline keyword.
You could use a template-template parameter
template<template<class, class> class M, class K, class V>
inline void PrintCollection(const M<K, V>& map, /* rest as before */)
{
// rest as before
}
int main()
{
std::map<int, int> m1;
std::multi_map<int, int> m2;
// fill both maps
PrintCollection(m1);
PrintCollection(m2);
}
But as hansmaad is pointing out, you could also use a pair of iterators instead of the container as parameter. In general, you would prefer that solution if your PrintCollection is very generic and does not make use of the fact that it has a Key and Value type. OTOH, if your PrintCollection also needs to print that information in some future version, then you might want to use a template-template parameter that takes those two types as parameters.