Specialize function for map like containers - c++

I want specialize a function template for vector and map like containers. For vector I can do like below but I don't know how can I have a specialized version of the function that will be used only for map like containers.
#include <iostream>
#include <vector>
#include <map>
using namespace std;
template<typename Iterator>
void print(Iterator begin, Iterator end)
{
while (begin != end)
{
cout << *begin << endl; // compiler error for map like containers
++begin;
}
}
int main()
{
vector<int> noVec = { 1, 2, 3 };
print(noVec.begin(), noVec.end());
map<int, int> nosMap;
nosMap[0] = 1;
nosMap[1] = 2;
nosMap[3] = 3;
print(nosMap.begin(), nosMap.end());
return 0;
}
This question is similar but it suggests to use pair in vector which I don't want to do. I know the specialization can be done with SFINAE but don't know what condition to check for. It would be great if I can achieve this with C++ 11 type_traits.

The value_type of a map is some pair so you could check if the value_type of the iterator is a std::pair or not, e.g.
#include <vector>
#include <map>
#include <iostream>
template <typename>
struct is_pair : std::false_type
{ };
template <typename T, typename U>
struct is_pair<std::pair<T, U>> : std::true_type
{ };
template <typename Iter>
typename std::enable_if<is_pair<typename std::iterator_traits<Iter>::value_type>::value>::type
print(Iter begin, Iter end)
{
std::cout << "called with map-like" << std::endl;
for (; begin != end; ++begin)
{
std::cout << begin->second;
}
std::cout << std::endl;
}
template <typename Iter>
typename std::enable_if<!is_pair<typename std::iterator_traits<Iter>::value_type>::value>::type
print(Iter begin, Iter end)
{
std::cout << "called with vector-like" << std::endl;
for (; begin != end; ++begin)
{
std::cout << *begin;
}
std::cout << std::endl;
}
int main()
{
std::vector<int> vec { 1, 2, 3 };
std::map<int, int> map {{0, 0}, {1, 1}, {2, 4}, {3, 9}};
print(vec.begin(), vec.end());
print(map.begin(), map.end());
}
which prints
called with vector-like
123
called with map-like
0149

You don't need to specialize anything. All you have to do is to provide an overloaded output operator<< for std::pair, like the example below:
template<typename T1, typename T2>
std::ostream& operator<<(std::ostream &out, std::pair<T1, T2> const &mp) {
return (out << "(" << mp.first << ", " << mp.second << ")");
}
LIVE DEMO
edit:
The above solution however as #Benjamin Lindley suggested in the comments might conflict with other template overloads of the output operator<< for std::pair.
If this is the case, alternatively you could write in their own namespace (e.g., namespace detail) two template function overloads (e.g., print_elem), like the example below:
namespace detail {
template<typename T1, typename T2>
std::ostream& print_elem(std::ostream &out, std::pair<T1, T2> const &mp) {
return (out << "(" << mp.first << ", " << mp.second << ")");
}
template<typename T>
std::ostream& print_elem(std::ostream &out, T const &elem) {
return (out << elem);
}
}
and change your template print like the example below:
template<typename Iterator>
void print(Iterator begin, Iterator end)
{
while (begin != end) {
detail::print_elem(cout, *begin) << endl;
++begin;
}
}
LIVE DEMO

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

Generic `erase_if` method for container types

I'm trying to write a generic erase_if method that can be used on any container type to remove elements given a predicate. It should either use the erase-remove-idiom if the container allows it, or loop through the container and call the erase method. I also just want to provide the container itself, not the begin and end iterator separatly. That shall be handled by the method.
However I can't get the meta-template to work to distinguish between the two cases through SFINAE. I'm trying to check whether the method std::remove_if (or std::remove) would be well defined for a given type, but the value is either true for both vector and map (in which case the code won't compile) or false for both of them. I'm quite new to template meta-programming, so is there something I'm missing? Or maybe there's another, better solution?
Below is my example code:
#include <algorithm>
#include <iostream>
#include <iterator>
#include <map>
#include <type_traits>
#include <vector>
namespace my_std
{
using std::begin;
using std::end;
namespace detail
{
template <typename T>
// this is false for both vector and map
auto is_remove_compatible(int) -> decltype(std::remove_if(begin(std::declval<T&>()), end(std::declval<T&>()),
[](auto&&) { return false; }),
std::true_type{});
// this is true for both vector and map
// auto is_remove_compatible(int)
// -> decltype(std::remove(begin(std::declval<T&>()), end(std::declval<T&>()), std::declval<T::value_type&>()),
// std::true_type{});
template <typename T>
auto is_remove_compatible(...) -> std::false_type;
}
template <typename T>
using is_remove_compatible = decltype(detail::is_remove_compatible<T>(0));
template <typename T>
constexpr bool is_remove_compatible_v = is_remove_compatible<T>::value;
template <typename Cont, typename Pred>
std::enable_if_t<is_remove_compatible_v<Cont>> erase_if(Cont& container, Pred pred)
{
std::cout << "Using erase-remove\n";
container.erase(std::remove_if(begin(container), end(container), pred), end(container));
}
template <typename Cont, typename Pred>
std::enable_if_t<!is_remove_compatible_v<Cont>> erase_if(Cont& container, Pred pred)
{
std::cout << "Using loop\n";
for (auto it = begin(container); it != end(container);)
{
if (pred(*it))
it = container.erase(it);
else
++it;
}
}
}
template <typename T>
std::ostream& operator<<(std::ostream& out, std::vector<T> const& v)
{
if (!v.empty())
{
out << '[';
std::copy(v.begin(), v.end(), std::ostream_iterator<T>(out, ", "));
out << "\b\b]";
}
return out;
}
template <typename K, typename V>
std::ostream& operator<<(std::ostream& out, std::map<K, V> const& v)
{
out << '[';
for (auto const& p : v)
out << '{' << p.first << ", " << p.second << "}, ";
out << "\b\b]";
return out;
}
int main(int argc, int argv[])
{
auto vp = my_std::is_remove_compatible_v<std::vector<int>>;
auto mp = my_std::is_remove_compatible_v<std::map<int, int>>;
std::cout << vp << ' ' << mp << '\n';
std::vector<int> v = {1, 2, 3, 4, 5};
auto v2 = v;
my_std::erase_if(v2, [](auto&& x) { return x % 2 == 0; });
std::map<int, int> m{{1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}};
auto m2 = m;
my_std::erase_if(m2, [](auto&& x) { return x.first % 2 == 0; });
std::cout << v << " || " << v2 << '\n';
std::cout << m << " || " << m2 << '\n';
std::cin.ignore();
}
This idea is wrong for two reasons:
auto is_remove_compatible(int) -> decltype(std::remove_if(begin(std::declval<T&>()), end(std::declval<T&>()),
[](auto&&) { return false; }),
std::true_type{});
First, you can't have a lambda in an unevaluated context, so the code is ill-formed. Second, even if it wasn't ill-formed (which is easy to fix), remove_if() isn't SFINAE-friendly on anything. If you pass in something that isn't a predicate to remove_if, or a non-modifiable iterator, that's not required by the standard to be anything but a hard error.
The way I would do it right now is via the chooser idiom. Basically, have conditional sfinae based on a rank ordering:
template <std::size_t I> struct chooser : chooser<I-1> { };
template <> struct chooser<0> { };
template <class Cont, class Predicate>
void erase_if(Cont& container, Predicate&& predicate) {
return erase_if_impl(container, std::forward<Predicate>(predicate), chooser<10>{});
}
And then we just have our various erase_if_impl conditions, in order:
// for std::list/std::forward_list
template <class Cont, class Predicate>
auto erase_if_impl(Cont& c, Predicate&& predicate, chooser<3> )
-> decltype(void(c.remove_if(std::forward<Predicate>(predicate))))
{
c.remove_if(std::forward<Predicate>(predicate));
}
// for the associative containers (set, map, ... )
template <class Cont, class Predicate>
auto erase_if_impl(Cont& c, Predicate&& predicate, chooser<2> )
-> decltype(void(c.find(std::declval<typename Cont::key_type const&>())))
{
using std::begin; using std::end;
for (auto it = begin(c); it != end(c); )
{
if (predicate(*it)) {
it = c.erase(it);
} else {
++it;
}
}
}
// for everything else, there's MasterCard
template <class Cont, class Predicate>
void erase_if_impl(Cont& c, Predicate&& predicate, chooser<1> )
{
using std::begin; using std::end;
c.erase(std::remove_if(begin(c), end(c), std::forward<Predicate>(predicate)), end(c));
}

single template function to display contents of different container [duplicate]

This question already has answers here:
use template to print all the data of any container
(2 answers)
Closed 5 years ago.
I have a vector container constructed using:
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
And deque container constructed using:
deque<int> deq;
deq.push_front(1);
deq.push_front(2);
deq.push_front(3);
I want to have a template function that displays the content of the both of these containers and for all data-types. For handling with different data types I made a template function like this:
template <typename T>
void display(vector<T> vec)
{
for(int i = 0; i < vec.size(); ++i)
{
cout << vec[i] << endl;
}
cout << endl;
}
Currently its working for vector, How can I make it work for all containers like deque, and list?
Something along these lines, perhaps:
template <typename C>
void display(const C& container) {
std::copy(std::begin(container), std::end(container),
std::ostream_iterator<decltype(*std::begin(container))>(std::cout, "\n"));
}
I guess you might use range-based for-loop:
template<typename C>
void display(const C& container)
{
for (const auto& e : container)
{
std::cout << e << std::endl;
}
}
Many STL containers support ranges with pairs as value types so
#include <iostream>
#include <deque>
#include <vector>
#include <map>
using std::cout;
using std::endl;
namespace detail {
template <typename First, typename Second>
void print_element(const std::pair<First, Second>& pr) {
cout << pr.first << " " << pr.second << endl;
}
template <typename Type>
void print_element(const Type& element) {
cout << element << endl;
}
} // namespace detail
template <typename Container>
void print_all(const Container& container) {
for (const auto& element : container) {
detail::print_element(element);
}
}
int main() {
auto v = std::vector<int>{1, 2, 3};
auto d = std::deque<int>{1, 2, 3};
auto m = std::map<int, int>{{1, 2}};
print_all(v);
print_all(d);
print_all(m);
return 0;
}

C++: How to pass any iterable type as a function parameter

As an exercise I'm trying to implement Python's str.join method in C++. I will eventually add the function as a method of the std::string class but I figure getting it to work is more of a priority. I've defined the function as follows:
template<typename Iterable>
std::string join(const std::string sep, Iterable iter);
Is there any way that I can ensure that the Iterable type is actually iterable? E.g. I wouldn't want to receive an int or char..
In C++, rather than having one Iterable, we pass in an iterator (almost a pointer) to the front and the end of the range:
template<typename Iter>
std::string join(const std::string &sep, Iter begin, Iter end);
Note that the sep should be passed as const reference, as you don't need to copy it.
You don't need to worry about whether the Iter is actually an iterator, though. This is because the code will simply fail to compile if it doesn't work.
For example, suppose you implemented it like so (this is a bad implementation):
template<typename Iter>
std::string join(const std::string &sep, Iter begin, Iter end) {
std::string result;
while (begin != end) {
result += *begin;
++begin;
if (begin != end) result += sep;
}
return result;
}
Then the type passed in as Iter must have an operator++, an operator!=, and an operator* to work, which is the well understood contract of an iterator.
All standard c++ collections has begin() and end() member functions. You could make use of that fact to ensure that the passed argument is actually a collection (in your terminology - iterable) by some SFINAE (c++11 example):
#include <array>
#include <list>
#include <vector>
#include <map>
#include <string>
template <class Iterable>
auto join(const std::string sep, const Iterable& iterable) -> decltype(iterable.begin(), iterable.end(), std::string{}) {
(void)sep; // to suppress warning that sep isn't used
// some implementation
return {};
}
int main() {
join(";", std::array<int, 5>{});
join(";", std::list<int>{});
join(";", std::vector<float>{});
join(";", std::string{});
join(";", std::map<int, float>{});
//join(";", int{}); // does not compile as int is not a collection
}
[live demo]
You may use template template syntax and - if needed - use SFINAE to make sure that proper class members are existing:
#include <vector>
#include <list>
#include <string>
#include <map>
#include <ostream>
//! Single value containers.
template< template<class> class L, class T,
class EntryT = typename L<T>::value_type>
std::string my_join(const std::string_view sep, const L<T>& anyTypeIterable)
{
std::stringstream ss;
bool first = true;
for (const EntryT& entry : anyTypeIterable)
{
if (first) first = false;
else ss << sep;
ss << entry;
}
return ss.str();
}
//! std::map specialization - SFINAE used here to filter containers with pair value_type
template< template<class, class> class L, class T0, class T1,
class EntryT = typename L<T0, T1>::value_type,
class FirstT = typeof(EntryT::first),
class SecondT = typeof(EntryT::second)>
std::string my_join(const std::string_view sep, const L<T0, T1>& anyTypeIterable)
{
std::stringstream ss;
bool first = true;
for (const EntryT& entry : anyTypeIterable)
{
if (first) first = false;
else ss << sep;
ss << entry.first << sep << entry.second;
}
return ss.str();
}
int main()
{
std::cout << my_join("; ", std::vector<int>({1, 2, 3, 4})) << std::endl;
std::cout << my_join("; ", std::list<int>({1, 2, 3, 4})) << std::endl;
std::cout << my_join("; ", std::string("1234")) << std::endl;
std::cout << my_join("; ", std::map<int, int>({ {1, 2}, {3, 4} })) << std::endl;
return 0;
}
// Output:
// 1; 2; 3; 4
// 1; 2; 3; 4
// 1; 2; 3; 4
// 1; 2; 3; 4
From https://devblogs.microsoft.com/oldnewthing/20190619-00/?p=102599 :
template<typename C, typename T = typename C::value_type>
auto do_something_with(C const& container)
{
for (int v : container) { ... }
}
Or if the container doesn't implement value_type:
template<typename C, typename T = std::decay_t<decltype(*begin(std::declval<C>()))>>
auto do_something_with(C const& container)
{
for (int v : container) { ... }
}
Or if you want only containers containing types convertible to int:
template<typename C, typename T = std::decay_t<decltype(*begin(std::declval<C>()))>,
typename = std::enable_if_t<std::is_convertible_v<T, int>>>
auto do_something_with(C const& container)
{
for (int v : container) { ... }
}
But see the comments in that blog post for more refinements.

passing template function to std::for_each

I am trying to write a simple template function that prints every element of some container, without using for loops. So far, I have
#include <iostream>
#include <vector>
#include <algorithm>
template <typename T> void print_with_space(T x){
std::cout << x << ' ';
}
template <typename T> void print_all(T beg, T end){
std::for_each(beg, end, print_with_space<int>);
std::cout << '\n';
}
int main(){
int a[] = {1, 2, 3};
std::vector<int> v(a, a+3);
print_all(v.begin(), v.end());
return 0;
}
The code compiles and runs, but only because I put print_with_space<int> inside the implementation of print_all. I would like to just have print_with_space there for obvious reasons, but then the code doesn't compile. How do I do this?
You can use:
std::for_each(beg, end, [](const typename T::value_type& value) {
print_with_space(value);
});
T is of type std::vector<>::iterator, which is a RandomAccessIterator. Every RandomAcessIterator has a underlying type, which is exposed by value_type.
So, if you pass std::vector<int>::iterator, std::vector<int>::iterator::value_type would be an int.
Now that you have the type, you can make a lambda, which will get executed for every iteration.
In C++14, you can even do:
//'auto' automatically deduces the type for you
std::for_each(beg, end, [](const auto& value) {
print_with_space(value);
});
Another option:
template <typename T> void print_all(T beg, T end) {
std::for_each(beg, end, print_with_space<decltype(*beg)>);
std::cout << '\n';
}
Alternative for C++03:
#include <iterator>
template <typename T> void print_all(T beg, T end)
{
typedef typename std::iterator_traits<T>::value_type val_t;
std::for_each(beg, end, print_with_space<val_t>);
std::cout << '\n';
}
The most flexible solution, which will work with all versions of c++, is to make print_with_space a function object.
This confers a number of advantages:
no need to specify template type at call site.
no need to fiddle around with manual type deduction.
partial specialisation can be achieved by having the functor defer to a templated free function.
Such as:
#include <iostream>
#include <iomanip>
#include <vector>
#include <algorithm>
// basic implementation
template<class T> void impl_print_with_space(const T& x)
{
std::cout << x << ' ';
}
// what about special handling for strings?
template<class C, class Ch, class Alloc>
void impl_print_with_space(const std::basic_string<C, Ch, Alloc>& x)
{
std::cout << std::quoted(x) << ' ';
}
// functor
struct print_with_space
{
template<class T> void operator()(const T& x) const
{
impl_print_with_space(x);
}
};
template <typename Iter> void print_all(Iter beg, Iter end)
{
std::for_each(beg, end, print_with_space());
std::cout << '\n';
}
int main(){
int a[] = {1, 2, 3};
std::vector<int> v(a, a+3);
print_all(v.begin(), v.end());
auto b = std::vector<std::string> { "hello", "world" };
print_all(b.begin(), b.end());
return 0;
}