When designing a function that expects a contiguous array in the form of a pointer, should one pass the array using

A.

a pointer and the array's size:

void foo(int* arr, size_t size);

Or B.

a begin-pointer and an end-pointer:

void foo(int* arrBegin, int* arrEnd);

Obviously the total size of the parameters are the same in both methods but is there ever a good reason to prefer one over the other? I believe approach A. is the more common one but I see both being used frequently. Could one make more sense in certain contexts, and vice versa? Does one have any drawbacks compared to the other? Or does it just not matter and you should simply choose one approach and stick with it?

2

There are 2 best solutions below

0
Captain Giraffe On

There is the general rule of least surprise

If I write a function like (Like @Elijay suggests)

template<typename RandomIt>
void f(RandomIt first, RandomIt last)...

I can be certain that it is well understood by everyone using my function, contiguous container, pointer like argument type etc.

On top of that first, and last is a shoe-in for pretty much all of the standard library algorithms and containers. Very convenient and easy to understand.

My 2¢.

0
H.S. On

First of all use of plain C-Style array is discouraged in C++. But, if due to some specific reason, you still want to use it then it's your choice and if its only for array then either approach is fine.

I would personally prefer the approach B i.e. the start and end iterator (using iterator instead of bare pointer) approach because it is more instinctive when the data is contiguous.

Also approach B has one advantage over approach A - assume, in future, you need to replace the plain C-Style array with container like vector or array everywhere in your code, the approach B is more flexible because you don't need to change anything in the place from where foo() is called.

e.g. Take first approach - approach A, iterator and size:

foo() implementation:

template <typename Iterator>
void foo1 (Iterator itrB, std::size_t sz) {
    // iterate over elements
    for (std::size_t i = 0; i < sz; ++i) {
        // access element as itrB[i]
        ....
        ....
    }
}

If you call foo() with plain C-Style array, this is how it would be:

    int arr[] = {1,2,3,4,5};

    foo(std::cbegin(arr), sizeof (arr) / sizeof (*arr));

Assume, you need to replace the plain C-Style array with vector, this is how you need to call:

    std::vector<int> arr = {1,2,3,4,5};

    foo(std::cbegin(arr), arr.size());
                          ^^^^^^^^^^^

You need to modify the foo() call everywhere for array size argument.

Same is the case with array container.

Now, take the second approach - approach B, start and end iterator:

foo() implementation:

template <typename Iterator>
void foo (Iterator itrB, Iterator itrE) {
    // iterate over elements
    for (auto b = itrB; b != itrE; ++b) {
        // access element as *b
        ....
        ....
    }
}

call foo() with plain C-Style array, this is how it would be:

    int arr[] = {1, 2, 3, 4, 5};

    foo (std::cbegin(arr), std::cend(arr));

with vector :

    std::vector<int> arr = {1, 2, 3, 4, 5};

No change is required in the foo() call, the same call statement will work:

    foo (std::cbegin(arr), std::cend(arr));

Note that std::cbegin() and std::cend() introduced in C++14, if you are using C++11 then replace them with std::begin() and std::end() respectively.