I've also given this a stab. I must say I quite like what one is able to do in c# in that case.
I've tried to stay clear of inheritance and the answer here elaborates why. In general I rarely inherit implementation (or extend as c# calls it).
One can create a class like IndexCheckable below that wraps the container. Presently it is only implemented for vector, but one can easily implement it for other containers too (and use other algorithms to determine the index).
IndexCheckable has a clear responsibility and gives checkability to it's container. One can also hide it behind an interface that hides the type of the container (not shown in example)
#include <iostream>
#include <vector>
#include <algorithm>
template <class T, class Allocator = std::allocator<T>>
struct IndexCheckable {
const std::vector<T,Allocator>& indexable_;
IndexCheckable(const std::vector<T,Allocator>& indexable)
: indexable_(indexable) {
}
int indexOf(const T& item) const {
auto i = std::find(indexable_.begin(), indexable_.end(), item);
return i == indexable_.end() ? -1 : i - indexable_.begin();
}
};
template <class T, class Allocator =
std::allocator<T>> IndexCheckable<T,Allocator> indexCheckable(const std::vector<T,Allocator>& indexable) {
return IndexCheckable<T,Allocator>{indexable};
}
void foo(const IndexCheckable<int>& indexCheckable) {
//Voilla. Check works!!!
indexCheckable.indexOf(5);
}
int main() {
std::vector<int> ivect{0, 1, 2, 5, 6};
foo(ivect);
// or...
auto r1 = indexCheckable(ivect).indexOf(5);
std::cout << "r1: " << r1 << std::endl; //Expects 3
auto r2 = indexCheckable(ivect).indexOf(10);
std::cout << "r2: " << r2 << std::endl; //Expects -1
return 0;
}
I've also decided to elaborate on the idea of hiding IndexCheckable behind and interface and implementing this for different containers. From a usage perspective, a client of the code just has to specify that something must be IndexCheckable (and oblivious of the container type):
#include <iostream>
#include <vector>
#include <list>
#include <array>
#include <algorithm>
#include <iterator>
template <class T>
struct IndexCheckable {
virtual int indexOf(const T& item) const = 0;
protected: ~IndexCheckable(){}
};
template <
template <class, class> class C, //Container
class T, //Item
class A = std::allocator<T>
>
struct IndexCheckableImpl : public IndexCheckable<T> {
const C<T, A>& indexable_;
IndexCheckableImpl(const C<T, A>& indexable)
: indexable_(indexable) {
}
int indexOf(const T& item) const override {
auto i = std::find(indexable_.begin(), indexable_.end(), item);
return i == indexable_.end() ? -1 : std::distance(indexable_.begin(), i);
}
};
template <template<class,class> class C, class T, class A = std::allocator<T>>
IndexCheckableImpl<C,T,A> indexCheckable(const C<T,A>& indexable) {
return IndexCheckableImpl<C,T,A>{indexable};
}
void testItem(const IndexCheckable<int>& indexCheckable, const char* name) {
auto r1 = indexCheckable.indexOf(5);
std::cout << "Test " << name << ": r1: " << r1 << std::endl; //Expects 3
auto r2 = indexCheckable.indexOf(10);
std::cout << "Test " << name << ": r2: " << r2 << std::endl; //Expects -1
}
template <class Container>
void test(const Container& container, const char* name) {
auto checkable = indexCheckable(container);
testItem(checkable, name);
}
int main() {
test(std::vector<int>{0, 1, 2, 5, 6}, "vector");
test(std::list<int>{0, 1, 2, 5, 6}, "list");
return 0;
}