I would like to write a variadic template function that takes an arbitrary number of containers of any type (though realistically I will only use it for vectors I would say), and return true
if they all have the same size
(so .size()
method or std::size
(same thing)). I tried to implement this using my own understanding of template programming but I failed. I tried to use a set of recursive functions to this end but without luck, it's clear to me that the wrong functions are being chosen by the compiler.
My implementation attempt, with reasoning
This is the function that I would like to be called first, and is the entry point. I have two arguments, one normal template argument and another as a parameter pack. This is so I can separate the "head" from the "tail" (which I am used to with car and cdr from lisp, maybe this is the wrong paradigm here though).
template <typename Arg, typename... Args>
bool all_equal_in_size(Arg arg, Args... args) {
auto size = arg.size();
return (size == all_equal_in_size(size, args...));
}
This should be the function called recursively, same as above but with the state (size) provided additionally in the parameter list. This is also familiar to me from functional programming, as a means to propagate state through a recursive call chain.
template <typename Arg, typename... Args>
bool all_equal_in_size(std::size_t n, Arg arg, Args... args) {
return ((n == arg.size()) && all_equal_in_size(args...));
}
This is simply the terminal condition, and ends the recursive call chain.
template <typename T> bool all_equal_in_size(unsigned long n, T ele) {
return (n == ele.size());
}
The Problem
So in essence I would like to use it like this (sorry if ctor args are wrong, point is I want some containers with length 5):
std::vector<int> v1(5);
std::vector<int> v2(5);
std::vector<float> v3(5);
std::vector<double> v4(5);
std::list<int> v5(5);
all_of_equal_size(v1, v2, v3, v4, v5); // == true
But when I try to do this I find that the compiler complains because the wrong functions are found:
bunch.hpp:13:24: error: member reference base type 'unsigned long' is not a structure or union
auto size = arg.size();
It's trying to call size
on a long, which I don't want. If I reorder the template definitions then I get even more scary compiler warnings. So where am I going wrong? Is there a simpler way to do this? Why is it picking the wrong template?
Thank you for reading.
CodePudding user response:
With template and overload, you have to be careful with order (if you don't provide forward declaration).
template <typename Arg, typename... Args>
bool all_equal_in_size(Arg arg, Args... args) {
auto size = arg.size();
return (size == all_equal_in_size(size, args...)); // only see itself, and not other won't be found by ADL
}
in addition, you miss one case, in:
template <typename Arg, typename... Args>
bool all_equal_in_size(Arg arg, Args... args)
{
auto size = arg.size();
return (size == all_equal_in_size(size, args...));
}
With empty pack, you call all_equal_in_size(size)
which can match only itself, adding
bool all_equal_in_size(std::size_t n) { return true; }
solves that.
So you have to order definition in right order (or providing forward declaration of the methods) and add missing overload:
You can drop recursion with the unique function:
template <typename Arg, typename... Args>
bool all_equal_in_size(Arg arg, Args... args) {
std::size_t sizes[] = {arg.size(), args.size()...};
return std::all_of(sizes,
sizes 1 sizeof...(Args),
[&](std::size_t size){ return size == std::size(arg); });
}
Demo or in C 17
template <typename Arg, typename... Args>
bool all_equal_in_size(Arg arg, Args... args) {
return ((std::size(arg) == std::size(args)) && ... && true);
}
CodePudding user response:
With C 17 you can use fold expressions:
template <typename Arg, typename... Args>
bool all_equal_in_size(Arg const& head, Args const&... tail){
return ( (tail.size() == head.size()) && ... );
}