bpeikes
bpeikes

Reputation: 3705

Is it possible to have C++ template function which takes any collection of type T?

I was wondering if there is a way to create a template function which takes a reference to ANY collection of a specific type. i.e. something like:

class Bob
{
  public:
  int age;
  int height;
}

template<class T>
void functionWhichIteratesOverBobs(T &bobs)
{
    int totalAge = 0;
    for(auto &bob: bobs)
    {
         totalAge += bob.age;
    }
}

Basically is there a way to, in the definition of the template function, require that T have a begin() and end() function which return an iterator to T.

I've seen the following question, but that would require a function which takes a begin and end, i.e.

std::vector<Bob> bobs;
functionWhichIteratesOverBobs(bob.begin(), bob.end());

when what I would like is:

std::vector<Bob> bobs;
functionWhichIteratesOverBobs(bobs);

Function that takes an STL iterator over ANY container of a elements of a specific type

Upvotes: 4

Views: 1160

Answers (4)

stephane k.
stephane k.

Reputation: 1788

With C++ 20 you can use the range concept:

#include <ranges>
template<std::ranges::range T>
void functionWhichIteratesOverBobs(T &bobs)

This will check that T is a range, i.e. has begin and end.

There are more fine grained concepts in the range library.

https://en.cppreference.com/w/cpp/ranges#Range_concepts

I find this much more readable than SFINAE

Upvotes: 0

iammilind
iammilind

Reputation: 70038

Yes. It's possible.

As Ed S. comment, just use the same function which you wrote in your question. If the bobs doesn't have begin() and end() then compiler will notify you by generating error.
Demo.

Upvotes: 0

ecatmur
ecatmur

Reputation: 157424

The C++ concept Container has a member type value_type the type of the contained elements, so you can use SFINAE just as you would with an iterator - it's even simpler, because you don't need to use iterator_traits:

template<class T>
auto functionWhichIteratesOverBobs(T &bobs)
    -> std::enable_if_t<std::is_same_v<typename T::value_type, Bob>>
{
    // ...
}

Note that this won't work with raw arrays; if you want those to work you would use decltype(std::begin(bobs)) and iterator_traits:

template<class T>
auto functionWhichIteratesOverBobs(T &bobs) -> std::enable_if_t<std::is_same_v<
    typename std::iterator_traits<decltype(std::begin(bobs))>::value_type, Bob>>
{
    // ...
}

Upvotes: 0

Columbo
Columbo

Reputation: 60999

If you're looking to keep the overload set unpolluted, use expression SFINAE like so:

template<class T>
void functionWhichIteratesOverBobs(T &bobs)
  -> decltype(std::begin(bobs), std::end(bobs), void()) {
    // [..Range based for over bobs..] 
}

Keep in mind though that the function template shown by you won't instantiate without an error if given a wrong argument, so this is currently superfluous - until you start overloading functionWhichIteratesOverBobs.

Upvotes: 1

Related Questions