Function which accept any STL container - c++

I need to implement a template function that accepts any STL container. And based on what kind of container to perform certain actions.
Example:
template <class Container, class T>
void func(Container<T> container) {
if (container == std::map) {
...
} else {
...
}
}
int main() {
std::vector<int> v1;
func(v1); // ok
std::vector<double> v2;
func(v2); // ok
std::map<int, double> m1;
func(m1); // ok
std::list<int> l1;
func(l1); // ok
}

You can apply Constexpr If (since C++17) (with std::is_same) to check the type at compile-time, and apply parameter pack (since C++11) because these containers take multiple template parameters. e.g.
template <template <typename...> class Container, class... T>
void func(const Container<T...>& container) {
if constexpr (std::is_same_v<Container<T...>, std::map<T...>>) {
...
} else if constexpr (std::is_same_v<Container<T...>, std::vector<T...>>) {
...
} else if constexpr (std::is_same_v<Container<T...>, std::list<T...>>) {
...
} else {
...
}
}
PS: It depends on your intent but changing the parameter to pass-by-reference-to-const to avoid unnecessary copy might be a good idea.

Since you're making one implementation per container anyway, you could make overloads directly. It'll work in C++11 and has the benefit of having the template parameters easily available in each overload.
template <class T, size_t N>
void func(const std::array<T,N>& c) {
std::cout << "array " << c.size() << '\n';
}
template <class T, class Alloc>
void func(const std::vector<T,Alloc>& c) {
std::cout << "vector " << c.size() << '\n';
}
template <class T, class Alloc>
void func(const std::list<T,Alloc>& c) {
std::cout << "list " << c.size() << '\n';
}
template <class Key, class T, class Comp, class Alloc>
void func(const std::map<Key,T,Comp,Alloc>& c) {
std::cout << "map " << c.size() << '\n';
}
template <class CharT, class Traits, class Alloc>
void func(const std::basic_string<CharT,Traits,Alloc>& c) {
std::cout << "basic_string " << c.size() << '\n';
}
// add more of the containers you aim to support here

Related

How to pass reference of noncopyable type to SFINAE "catch-all" overload function?

I want to handle noncopyable type by reference when SFINAE get unkown input, my code below can't work, is there a better way?
#include <iostream>
#include <functional>
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
void data_type(T const& t) {
std::cout << "integer" << std::endl;
}
void data_type(...) {
std::cout << "catch unknown" << std::endl;
}
int main() {
struct noncopyable_type {
int i;
noncopyable_type() {}
noncopyable_type(const noncopyable_type&) = delete;
};
int i;
noncopyable_type s;
// first try
data_type(i); // ok
data_type(s); // error: call to deleted constructor
// try again
data_type(std::cref(i)); // ok, but the type is std::reference_wrapper, not integer
data_type(std::cref(s)); // ok
}
There are probably many ways, this is the first one that came to mind. Live demo
#include <iostream>
#include <functional>
template <typename T,
typename = typename std::enable_if<std::is_integral<T>::value>::type>
void data_type(T const& t) {
std::cout << "integer" << std::endl;
}
template <typename ... T,
typename = typename std::enable_if<sizeof...(T)==1>::type>
void data_type(T const&...) {
std::cout << "unknown" << std::endl;
}
int main() {
struct noncopyable_type {
noncopyable_type() {}
noncopyable_type(const noncopyable_type&) = delete;
};
int i;
noncopyable_type s;
// first try
data_type(i); // ok
data_type(s); // ok
}
In C++17 I would just use if constexpr.
We rarely need to use the ... trick anymore. With concepts, we can get the overload resolution behaviour we need without having to play tricks with the parameter declaration clause:
template <typename T>
requires std::integral<T>
void data_type(T const& t) {
std::cout << "integer" << std::endl;
}
template <typename T>
void data_type(T const& t) {
std::cout << "unknown" << std::endl;
}
The first overload is more constrained than the second one, so the second one will only be used when the first one is not applicable due to its constraint not being satisfied.
Note that the first overload may equivalently be written like so:
template <std::integral T>
void data_type(T const& t) {
std::cout << "integer" << std::endl;
}
It's very unclear to me what the actual problem you're trying to solve is, but there's a few possibly better ways to approach this.
With if constexpr
template <typename T>
void data_type(T const&) {
if constexpr (std::is_integral_v<T>) {
std::cout << "integral\n";
} else {
std::cout << "unknown\n";
}
}
If (as I suspect) your goal is to not bind a reference to integral types (for whatever reason) you can get fancier with C++20 concepts
template <std::integral T>
void data_type(T) {
std::cout << "integral\n";
}
template <typename T> requires (!std::integral<T>)
void data_type(T const&) {
std::cout << "unknown\n";
}

Why compiler cannot deduce template template argument?

I want to write a function that takes a generic container with any type and prints it.
Let's leave for a moment that it won't work for some associative containers and focus on the issue:
template<template<typename> typename Cont, typename T>
void print(const Cont<T>& cont)
{
for (const auto it : cont)
{
cout << it << " ";
}
cout << endl;
}
int main()
{
vector<string> v;
print(v);
}
The error states:
error C2672: 'print': no matching overloaded function found
error C3207: 'print': invalid template argument for 'Cont', class template expected
Can anyone please explain why the compiler cannot deduce the types here?
Even when I explicitly state print<vector<string>>(v)?
std::vector has more than one class-template-args.
template<
class T, // -----------> you have mentioned!
class Allocator = std::allocator<T> // -----> this didn't!
> class vector;
But you have provided only one. This is the reason for the no matching overloaded compiler error.
In order to fix the issue, you need to provide the variadic args in the template template arg.
template<template<typename...> typename Cont, typename T>
// ^^^^^^^^^^^^^^^^^^^^
void print(const Cont<T>& cont)
{
for (const auto& it : cont) {
std::cout << it << " ";
}
std::cout << std::endl;
}
(See a demo)
However, you could have done this simply
template <typename Cont>
void print(const Cont& cont)
{
// ...
}
Or like the standard way, passing the begin and end iterator of the container to the function
#include <algorithm> // std::copy
#include <iterator> // std::iterator_traits
template<typename Iterator>
constexpr void print(const Iterator begin, const Iterator end) noexcept
{
using ValueType = typename std::iterator_traits<Iterator>::value_type;
std::copy(begin, end, std::ostream_iterator<ValueType>(std::cout, " "));
std::cout << "\n";
}
and call it like
print(v.begin(), v.end());
std::vector is defined as
template<
class T,
class Allocator = std::allocator<T>
> class vector;
std::vector is a class template with two template parameters. It does not match
the template template parameter of your function.
Change it to
template <typename Cont>
void print(const Cont& cont)
{
for (const auto& elem : cont) {
std::cout << elem << " ";
}
std::cout << std::endl;
}
You can deduce T from the container, if that is necessary.

How to make template function specialization for vector and list types

I tried to implement template function specialization. You can run my tiny code in this fiddle. You can also see it below
#include <iostream>
#include <vector>
#include <list>
template <typename T>
struct is_vector {
static const bool value = false;
};
template <typename T>
struct is_vector<std::vector<T>> {
static const bool value = true;
using type = std::vector<T>;
};
template <typename T>
struct is_list {
static const bool value = false;
};
template <typename T>
struct is_list<std::list<T>> {
static const bool value = true;
using type = std::list<T>;
};
template<typename T, class = typename std::enable_if<is_list<T>::value>::type>
void foo(T t) {
std::cout << "is list" << std::endl;
}
/*
template<typename T, class = typename std::enable_if<is_vector<T>::value>::type>
void foo(T t) {
std::cout << "is vector" << std::endl;
}
*/
//The above code will cause an error, if we uncomment it
int main()
{
foo(std::list<int>{});
return 0;
}
In this code, I have several lines commented:
template<typename T, class = typename std::enable_if<is_vector<T>::value>::type>
void foo(T t) {
std::cout << "is vector" << std::endl;
}
If I uncomment it, I get "redifinition" error. I'm not sure how to fix it.
If I uncomment it, I get "redifinition" error. I'm not sure how to fix it.
The reason is quite simple: default values for template type arguments are not a part of a function signature. It means that you have the same template defined two times.
You might move SFINAE part in to the function return type, as it is suggested by other answers, or change the code to:
template<typename T, std::enable_if_t<is_list<T>::value, int> = 0>
void foo(T t) {
std::cout << "is list" << std::endl;
}
template<typename T, std::enable_if_t<is_vector<T>::value, int> = 1>
void foo(T t) {
std::cout << "is vector" << std::endl;
}
You can do this instead.
template<typename T>
typename std::enable_if<is_list<T>::value>::type foo(T t) {
std::cout << "is list" << std::endl;
}
template<typename T>
typename std::enable_if<is_vector<T>::value>::type foo(T t) {
std::cout << "is vector" << std::endl;
}
Not sure if this is what you are after, but you could just check if either list or vector is a matching type:
template<typename T, class = typename std::enable_if<is_list<T>::value || is_vector<T>::value>::type>
void foo(T t) {
std::cout << "is list" << std::endl;
}
Updated fiddle: https://godbolt.org/g/oD3o9q
Update (for C++14):
template<typename T, class = std::enable_if_t<is_list<T>::value || is_vector<T>::value>>
void foo(T t) {
std::cout << "is list" << std::endl;
}

Can C++ enable_if have a default implementation?

I want to write a function print which behaves differently according to the type of its argument.
Here is my implementation:
template <typename T, typename std::enable_if<std::is_array<T>::value, int>::type = 0>
void print(const T &v) {
std::cout << "array: ";
for (const auto &e : v) {
std::cout << e << ", ";
}
std::cout << std::endl;
}
template <typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
void print(const T &v) {
std::cout << "integral: " << v << std::endl;
}
template <typename T, typename std::enable_if<!(std::is_array<T>::value || std::is_integral<T>::value), int>::type = 0>
void print(const T &v) {
std::cout << "default: " << v << std::endl;
}
This code works as expected, but the conditions in the last specification are too complicated.
Is there any solution to simplify the last one?
A general approach you can use for a default case is to have a function which takes a variable argument list. This will only be used if no other function matches. Here is an example:
template <typename T, typename std::enable_if<std::is_array<T>::value, int>::type = 0>
void print_helper(const T &v,int) {
std::cout << "array: ";
for (const auto &e : v) {
std::cout << e << ", ";
}
std::cout << std::endl;
}
template <typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
void print_helper(const T &v,int) {
std::cout << "integral: " << v << std::endl;
}
template <typename T>
void print_helper(const T &v,...) {
std::cout << "default: " << v << std::endl;
}
template <typename T>
void print(const T &v)
{
print_helper(v,0);
}
For only two overloads, the extra function may not be worth it, but as you get more overloads this form can really pay off for the default case.
We can use an extra chooser to make things better for us, courtesy of Xeo:
struct otherwise{ otherwise(...){} };
template<unsigned I>
struct choice : choice<I+1>{};
// terminate recursive inheritence at a convenient point,
// large enough to cover all cases
template<> struct choice<10>{};
Then each ranking on our choice list will be preferred to the next, and we just have to disable as we go:
// just forward to our first choice
template <class T> void print(const T &v) { print(v, choice<0>{}); }
Where our top choice is array:
template <class T, class = std::enable_if_t<std::is_array<T>::value>>
void print(const T& v, choice<0> ) { ... }
And then integral:
template <class T, class = std::enable_if_t<std::is_integral<T>::value>>
void print(const T& v, choice<1> ) { ... }
And then anything
template <class T>
void print(const T& v, otherwise ) { ... }
This structure allows for arbitrarily many choices.

C++ correct syntax for triple-nested template parameter

I'm confused why the below code gets rejected by the compiler. Some help please?
template<template<template<class> class> class Ptr,
template<class> class Container, class T>
inline void print(Ptr<Container<T>> l) {
std::cout << '[';
for (auto it = l->begin(); it != l->end() - 1; ++it) {
std::cout << *it << ", ";
}
std::cout << l->back() << ']' << std::endl;
}
I got confused of what works and what not in #RSahu answer and whether it solves the problem in general.
But, inspired by it, I get the following solution. Which simply exploits the fact that the class template class can be variadic. So for example is now compatible with std::vector<T, A> or other classes (containers hopefuly) that have more or less template parameters. In fact it is useful to do the same with the Ptr parameter so it is also compatible with std::unique_ptr that takes more template parameters than std::shared_ptr.
http://coliru.stacked-crooked.com/a/40d43526ed77eb9d
#include <iostream>
#include <vector>
#include <memory>
template<template<class...> class Ptr, template<class...> class Container, class T>
inline void f(Ptr<Container<T>> p) {
std::cout << "bla" << std::endl;
}
int main() {
f(std::make_shared<std::vector<int>>());
}
Note: having said that, I think there are better ways to constrain the argument types than forcing patterns.
For example, this could be an alternative:
template<class PtrContainerT, typename = decltype( std::declval<PtrContainerT>()->back())> //probably pretty much constrains the intended use
inline void f(PtrContainerT&& p) {
using T = typename PtrContainerT::element_type::value_type; //in case you need to know T
std::cout << "bla" << std::endl;
}
http://coliru.stacked-crooked.com/a/fbe43aab94364764
or even more to the point (although not 100% equivalent) :
template<class PtrContainerT, typename T = typename PtrContainerT::element_type::value_type> //probably pretty much constrains the intended use
inline void f(PtrContainerT&& p) {
std::cout << "bla" << std::endl;
}
Use std::decay<PtrContainerT>::...etc if necessary.
I can think of the following two ways to resolve the problem.
template <template <template <class> class> class Ptr,
template<class> class Container, class T>
inline void print(Ptr<Container> l) {
// ^^^ Not Ptr<Container<T>>
std::cout << '[';
for (auto it = l->begin(); it != l->end() - 1; ++it) {
std::cout << *it << ", ";
}
std::cout << l->back() << ']' << std::endl;
}
or
template <template<class> class Ptr,
// One less level of template for Ptr
template<class> class Container, class T>
inline void print(Ptr<Container<T>> l) {
// Containe<T> is a class
std::cout << '[';
for (auto it = l->begin(); it != l->end() - 1; ++it) {
std::cout << *it << ", ";
}
std::cout << l->back() << ']' << std::endl;
}
Looking at your code, it not clear which one will work for you.
Update
The following code does not work
#include <vector>
#include <memory>
template<template<class> class Ptr, template<class> class Container, class T>
inline void f(Ptr<Container<T>> p) {}
int main() {
f(std::make_shared<std::vector<int>>());
}
since std::vector is defined as:
template<
class T,
class Allocator = std::allocator<T>
> class vector;
It is not
template<
class T,
> class vector;
This works:
template<template<class> class Ptr, template<class> class Container, class T>
inline void f(Ptr<Container<T>> p) {}
template <typename T> using MyVector = std::vector<T>;
int main() {
f<std::shared_ptr, MyVector, int>(std::make_shared<MyVector<int>>());
}
This also works:
template<template<class> class Ptr, template<class> class Container, class T>
inline void f(Ptr<Container<T>> p) {}
template <typename T> struct Foo {};
int main() {
f(std::make_shared<Foo<int>>());
}