How to write a function that can take in an array or a vector?

Viewed 3900

I would like to write a C++ function with one argument such that one can pass in either any of the following types:

std::vector<int>
std::array<int>
int array[numElements]
int *ptr = new int[numElements]
etc

Would templating be the best way to accomplish this?

5 Answers

If you expect to just be able to do func(v) you cannot, because there's no way I can think of that your function could deduce the size of the dynamically allocated int[numElements].

A good way you could wrap this is to take a pair of forward iterators, that is, if you only need iterating over items one by one, since random access is very bad on some containers like std::list.

template<class FWIt>
void func(FWIt a, const FWIt b)
{
    while (a != b)
    {
        std::cout << "Value: " << *a << '\n';
        ++a;
    }
}

template<class T>
void func(const T& container)
{
    using std::begin;
    using std::end;
    func(begin(container), end(container));
}

This would work with the following:

int array[5] = {1, 2, 3, 4, 5};
func(array);

int* dynarray = new int[5]{1, 2, 3, 4, 5};
func(dynarray, dynarray + 5);

std::vector<int> vec{1, 2, 3, 4, 5};
func(vec);
func(vec.begin(), vec.end());

std::list<int> list{1, 2, 3, 4, 5};
func(list);

Edit: This also works by passing raw arrays directly rather than as two pointers thanks to @DanielH's change (but still won't work with dynamically allocated arrays).

span seems to be what you are looking for. Either wait for C++20 :-) or use span from the GSL. See What is a “span” and when should I use one? . Example below.

You didn't say if you wanted an argument that take a writable or read-only version of the indata so the example shows both.

#include <array>
#include <cstdlib>
#include <iostream>
#include <memory>
#include <vector>

#if defined(__has_include) && __has_include(<span>)
#include <span>
#endif

#ifdef __cpp_lib_span
using std::span;
#else
#include <gsl/gsl>
using gsl::span;
#endif

void funcDoSomething(span<int> data) {
    int value{41};
    for (auto &i: data) {
        i += value;
        ++value;
    }
}

void funcReadOnly(span<int const> data) {
    for (auto &i: data) {
        // ++i; won't compile since we have read-only span values
        std::cout << i << ' ';
    }
    std::cout << '\n';
}

int main() {
    std::vector<int> stdvec{10, 11, 12};
    funcDoSomething(stdvec);
    funcReadOnly(stdvec);

    std::array<int, 3> stdarr{20, 21, 22};
    funcDoSomething(stdarr);
    funcReadOnly(stdarr);

    int carr[3]{30, 31, 32};
    funcDoSomething(carr);
    funcReadOnly(carr);

    auto ptr = std::unique_ptr<int[]>(new int[3]);
    ptr[0] = 40;
    ptr[1] = 41;
    ptr[2] = 42;
    funcDoSomething({ptr.get(), 3});
    funcReadOnly({ptr.get(), 3});

    return EXIT_SUCCESS;
}

Would templating be the best way to accomplish this?

It depends. If you're writing a function that goes in a header, and thus can be used for further compilation later, then - yes, probably:

template <typename IntContainer>
void f(Container& c);

or

template <typename IntContainer>
void f(const Container& c);

However, if the implementation is compiled just once, you should consider:

void f(gsl::span<int> sp);

or

void f(gsl::span<const int> sp);

which uses a span. If you haven't heard about them, read:

What is a "span" and when should I use one?

this function will be able to take almost all of your variables as-is: The std::vector, the std::array and the plain (sized) array can be passed with no extra syntax. For the pointer, though, you will need to call something like f(gsl::make_span{ptr, numElements}).

PS - A third option, very common in the standard library, is to take interators, rather than the container, as the parameter. This would also require templating so it's similar to the first option.

You can not get all the listed types to one single function template. But, you could have function template overloads, which will solve the problem for std::vector<>, std::array<> and Type array[numElements].

template<typename Iter>
void funArray(const Iter begin, const Iter end) 
{
    std::cout << "Actual code here\n";
}

template<typename Container> void funArray(const Container& arr)
{
    funArray(std::begin(arr), std::end(arr)); //std::vector or std::array
}

Now you can write:

int main()
{
    const std::size_t numElements = 5;
    std::vector<int> vec;
    std::array<int, numElements> arr;
    int array[numElements];
    int *ptr = new int[numElements];

    funArray(vec);
    funArray(arr);
    funArray(array);
    funArray(ptr, ptr+numElements); 
    return 0;
}

However, for the dynamically allocated array, you need to use as user @Asu suggested.


Edit: Removed the redundant overload.

template<typename T, std::size_t N> void funArray(const T (&arr)[N]) {}

As @Daniel H pointed out that, the above function template overload for C type arrays, are futile, since it can be handled by the second overload(template<typename Container>), by deducing directly to C type arrays.

If they all use int then you could simply accept beginning and end pointers. You can use the standard algorithms on them because pointers are iterators.

void my_func(int const* begin, int const* end)
{
    std::for_each(begin, end, [](int i){
        std::cout << i << '\n';
    });
}

Then:

std::vector<int> v;
std::array<int> a;
int array[numElements];
int* ptr = new int[numElements];


my_func(v.data(), v.data() + v.size());

my_func(a.data(), a.data() + a.size());

my_func(std::begin(array), std::end(array));

my_func(ptr, ptr + numElements);

A benefit of passing two iterators (generic or otherwise) is that you do not have to pass the whole container to the algorithm.

Related