Как написать функцию, которая может принимать массив или вектор? - PullRequest
0 голосов
/ 11 ноября 2018

Я хотел бы написать функцию C ++ с одним аргументом, чтобы можно было передавать любой из следующих типов:

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

Будет ли использование шаблонов лучшим способом для этого?

Ответы [ 5 ]

0 голосов
/ 11 ноября 2018

Если все они используют int, вы можете просто принять начальный и конечный указатели.Вы можете использовать стандартные алгоритмы для них, потому что указатели являются итераторами .

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

Тогда:

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);

Преимущество передачи двух итераторов (общего или иного) заключается в том, что вам не нужно передавать весь контейнер алгоритму.

0 голосов
/ 11 ноября 2018

span, кажется, то, что вы ищете. Либо подождите C ++ 20 :-), либо используйте span из GSL. См. Что такое «промежуток» и когда я должен его использовать? . Пример ниже.

#include <array>
#include <iostream>
#include <vector>

#if __cplusplus > 201709L
#include <span>
using std::span;
#else
#include <gsl/gsl>
using gsl::span;
#endif

void func(span<int> data){
    for(auto i : data){
        std::cout << i << ' ';
    }
    std::cout <<'\n';
}

int main(){
    std::vector<int> stdvec(3);
    func(stdvec);
    std::array<int,3> stdarr;
    func(stdarr);
    int carr[3];
    func(carr);
    int *ptr = new int[3]();
    func({ptr,3});
    delete []ptr;
    return EXIT_SUCCESS;
}
0 голосов
/ 11 ноября 2018

Вы не можете получить все перечисленные типы в одном шаблоне функции. Но у вас могут быть перегрузки шаблонов функций, которые решат проблему для std::vector<>, std::array<> и 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
}

Теперь вы можете написать:

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;
}

Однако для динамически размещаемого массива вам нужно использовать, как предложил пользователь @Asu.


Редактировать : устранена избыточная перегрузка.

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

Как @ Daniel H указывал, что вышеуказанная перегрузка шаблона функции для массивов типа C бесполезна, поскольку она может быть обработана второй перегрузкой (template<typename Container>) , выводя непосредственно в массивы типа C.

0 голосов
/ 11 ноября 2018

Будет ли использование шаблонов лучшим способом для этого?

Это зависит. Если вы пишете функцию, которая идет в заголовке и, следовательно, может быть использована для дальнейшей компиляции позже, тогда - да, возможно:

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

или

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

Однако, если реализация скомпилирована только один раз, вы должны учитывать:

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

или

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

, который использует диапазон. Если вы не слышали о них, прочитайте:

Что такое «промежуток» и когда я должен его использовать?

эта функция сможет принимать почти все ваши переменные как есть: std::vector, std::array и обычный (размерный) массив могут быть переданы без дополнительного синтаксиса. Для указателя, однако, вам нужно будет вызвать что-то вроде f(gsl::make_span{ptr, numElements}).

PS - Третий вариант, очень распространенный в стандартной библиотеке, заключается в том, чтобы в качестве параметра брать целочисленные значения, а не контейнер. Это также потребует шаблонов, так что это похоже на первый вариант.

0 голосов
/ 11 ноября 2018

Если вы ожидаете, что сможете просто сделать func(v), вы не сможете этого сделать, потому что я никак не могу подумать о том, что ваша функция могла бы определить размер динамически распределенного int[numElements].

Хороший способ обернуть это - взять пару прямых итераторов, то есть, если вам нужно только перебирать элементы по одному, поскольку произвольный доступ очень плох для некоторых контейнеров, таких как 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));
}

Это будет работать со следующим:

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);

Редактировать: Это также работает, передавая необработанные массивы напрямую, а не в виде двух указателей, благодаря изменению @ DanielH (но все равно не будет работать с динамически размещаемыми массивами).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...