I want to implement a function that takes std::vector or std::array as an argument. How can the parameter list abstract from the container type?
See this example:
// how to implement this?
bool checkUniformity(container_type container)
{
for(size_t i = 1; i < container.size(); i++)
{
const auto& o1 = container[i-1];
const auto& o2 = container[i];
if(!o1.isUniform(o2))
return false;
}
return true;
}
struct Foo
{
bool isUniform(const Foo& other);
}
// I want to call it in both ways:
std::vector<Foo> vec;
std::array<Foo> arr;
bool b1 = checkUniformity(vec);
bool b2 = checkUniformity(arr);
What is the best and most readable way to do this?
Any suggestions for code improvement (style, design) are welcome as well. Thanks!
If you use iterators and ranges instead of working with the container directly, you can produce an algorithm that works with any container (including linked lists) efficiently and also with streams:
#include <list>
#include <array>
#include <vector>
#include <iterator>
template <typename T>
bool checkUniformity(T begin, T end) {
// Check for empty range
if (begin == end) return true;
// Remember last element
T last = begin;
while (++begin != end) {
if (!((*last).isUniform(*begin)))
return false;
last = begin;
}
return true;
}
template <typename T, typename F>
bool checkUniformity(T begin, T end, F pred) {
// Check for empty range
if (begin == end) return true;
// Remember last element
T last = begin;
while (++begin != end) {
if (!pred(*last, *begin))
return false;
last = begin;
}
return true;
}
struct Foo
{
bool isUniform(const Foo& other) const;
};
int main () {
// I want to call it in both ways:
std::vector<Foo> vec;
std::array<Foo, 3> arr;
std::list<Foo> list;
Foo carr [3];
bool b1 = checkUniformity(std::cbegin(vec), std::cend(vec));
bool b2 = checkUniformity(std::cbegin(arr), std::cend(arr));
bool b3 = checkUniformity(std::cbegin(list), std::cend(list));
bool b4 = checkUniformity(std::cbegin(carr), std::cend(carr));
bool b1_2 = checkUniformity(std::cbegin(vec), std::cend(vec), [] (const Foo& a, const Foo& b) { return a.isUniform(b); });
bool b2_2 = checkUniformity(std::cbegin(arr), std::cend(arr), [] (const Foo& a, const Foo& b) { return a.isUniform(b); });
bool b3_2 = checkUniformity(std::cbegin(list), std::cend(list), [] (const Foo& a, const Foo& b) { return a.isUniform(b); });
bool b4_2 = checkUniformity(std::cbegin(carr), std::cend(carr), [] (const Foo& a, const Foo& b) { return a.isUniform(b); });
}
You can also implement a second variant as shown, where you can specify the condition as a predicate (e.g. a lambda, as shown) in case you have different variants of isUniform. Having to pass two parameters for the range instead of just the container is slightly more cumbersome, but much more flexible; it also allows you to run the algorithm on a sub-range of the container.
This is the same approach as used by standard-library algorithms, such as std::find.
You want template:
template <typename container_type>
bool checkUniformity(const container_type& container)
{
for(size_t i = 1; i < container.size(); i++)
{
const auto& o1 = container[i-1];
const auto& o2 = container[i];
if(!o1.isUniform(o2))
return false;
}
return true;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With