Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write a size() function that works on any type of collection objects?

I require a simple way to obtain the count / length / size of an object of class T where T is some sort of collection type, such as a std::map, std::list, std::vector, CStringArray, CString, std::string, …

For most of the standard types, T::size() is the correct answer, for most of the MFC classes T::GetSize() is correct and for CString, it is T::GetLength().

I want to have a like:

template <typename T> auto size(const T & t)

...which evaluates to the correct member function call.

It seems like there should be a simple way to invoke a traits template on T which has a size(const T & t) member, which itself uses SFINAE to exist or not exist, and if it exists, then it is by definition calling an appropriate t.size_function() to return the count of elements in that instance of a T.

I could write an elaborate has_member type-trait template - there are a few examples on stackoverflow - all of them quite convoluted for what seems to me "there must be a simpler approach". With C++ 17, it seems like this issue should be easily and elegantly solved?

These discussions here and here seems to use an inelegant solution with some of the answers using preprocessor macros to get the job done. Is that still necessary?

But... surely, there must be a way to use the fact that calling the correct member function on a T is compilable, and calling the wrong one fails to compile - can't that be used directly to create the correct type traits wrapper for a given type T?


I would like something along the lines of:

template <typename T>
auto size(const T & collection)
{
    return collection_traits<T>::count(collection);
}

Where the exact specialization of collection_traits<T> is selected because it is the only one that fits for T (i.e. it calls the correct instance method).

like image 638
Mordachai Avatar asked Feb 06 '18 16:02

Mordachai


2 Answers

You can use expression SFINAE and multiple overloads.

The idea is as follows: check if x.size() is a valid expression for your type - if it is, invoke and return it. Repeat for .getSize and .getLength.

Given:

struct A { int size() const { return 42; } };
struct B { int getSize() const { return 42; } };
struct C { int GetLength() const { return 42; } };

You can provide:

template <typename T>
auto size(const T& x) -> decltype(x.size()) { return x.size(); }

template <typename T>
auto size(const T& x) -> decltype(x.getSize()) { return x.getSize(); }

template <typename T>
auto size(const T& x) -> decltype(x.GetLength()) { return x.GetLength(); }

Usage:

int main()
{
    size(A{});
    size(B{});
    size(C{});
}

live example on wandbox.org

This solution is easy to extend and seamlessly works with containers that are templatized.


What if a type exposes two getters?

The solution above would result in ambiguity, but it's easy to fix by introducing a ranking/ordering that solves that.

Firstly, we can create a rank class that allows us to arbitrarily prioritize overloads:

template <int N> struct rank : rank<N - 1> { };
template <>      struct rank<0> { };

rank<N> is implicitly convertible to rank<N - 1>. An exact match is better than a chain of conversions during overload resolution.

Then we can create a hierarchy of size_impl overloads:

template <typename T>
auto size_impl(const T& x, rank<2>) 
    -> decltype(x.size()) { return x.size(); }

template <typename T>
auto size_impl(const T& x, rank<1>) 
    -> decltype(x.getSize()) { return x.getSize(); }

template <typename T>
auto size_impl(const T& x, rank<0>) 
    -> decltype(x.GetLength()) { return x.GetLength(); }

Finally we provide an interface function that begins the dispatch to the right size_impl overload:

template <typename T>
auto size(const T& x) -> decltype(size_impl(x, rank<2>{})) 
{ 
    return size_impl(x, rank<2>{}); 
}

Using a type like D below

struct D
{
    int size() const { return 42; }
    int getSize() const { return 42; }
    int GetLength() const { return 42; }
};

will now choose the rank<2> overload of size_impl:

live example on wandbox

like image 85
Vittorio Romeo Avatar answered Sep 21 '22 03:09

Vittorio Romeo


The simplest solution, IMO, is function overloading.

// Default implementation for std containers.
template <typename Container>
std::size_t size(Container const& c) { return c.size(); }

// Overloads for others.
std::size_t size(CStringArray const& c) { return c.GetSize(); }
std::size_t size(CString const& c) { return c.GetLength(); }
// ... etc.
like image 38
R Sahu Avatar answered Sep 21 '22 03:09

R Sahu