Передавання функції масиву std :: невідомого розміру


98

У C ++ 11, як би я писав функцію (або метод), яка приймає масив std :: відомого типу, але невідомого розміру?

// made up example
void mulArray(std::array<int, ?>& arr, const int multiplier) {
    for(auto& e : arr) {
        e *= multiplier;
    }
}

// lets imagine these being full of numbers
std::array<int, 17> arr1;
std::array<int, 6>  arr2;
std::array<int, 95> arr3;

mulArray(arr1, 3);
mulArray(arr2, 5);
mulArray(arr3, 2);

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

Чи існує простий спосіб зробити цю роботу, як це було б із простими масивами у стилі С?


1
Масиви не мають меж перевірки чи знають, якого вони розміру. Тому ви повинні їх щось обернути або розглянути можливість використання std::vector.
Тревіс Пессетто,

20
Якщо шаблони здаються вам безладними та надмірними, вам слід пережити це почуття. Вони звичайні для С ++.
Бенджамін Ліндлі,

Будь-яка причина не використовувати std::vectorяк @TravisPessetto рекомендує?
Кори Кляйн,

2
Зрозуміло. Якщо це обмеження їхньої природи, я повинен змиритися з цим. Причиною того, що я думав про те, щоб уникнути std :: vector (що чудово підходить для мене), є те, що він виділений у купі. Оскільки ці масиви будуть крихітними та циклічними на кожній ітерації програми, я думав, що масив std :: може працювати трохи краще. Я думаю, що тоді я буду використовувати масив у стилі С, тоді моя програма не складна.
Адріан,

15
@Adrian Ваш спосіб думати про продуктивність абсолютно неправильний. Не намагайтеся робити мікрооптимізацію, перш ніж у вас є навіть функціональна програма. І після того, як у вас є програма, не здогадуйтесь про те, що слід оптимізувати, натомість нехай профіліст розповість вам, яку частину програми слід оптимізувати.
Paul Manta

Відповіді:


86

Чи існує простий спосіб зробити цю роботу, як це було б із простими масивами у стилі С?

Ні. Ви дійсно не можете цього зробити, якщо не зробите свою функцію шаблоном функції (або не використаєте інший тип контейнера, наприклад std::vector, як запропоновано в коментарях до запитання):

template<std::size_t SIZE>
void mulArray(std::array<int, SIZE>& arr, const int multiplier) {
    for(auto& e : arr) {
        e *= multiplier;
    }
}

Ось живий приклад .


9
OP запитує, чи є якесь інше рішення, крім шаблонів.
Новак

1
@Adrian: На жаль, іншого рішення немає, якщо ви хочете, щоб ваша функція працювала загально на масивах будь-якого розміру ...
Енді Проул,

1
Правильно: іншого шляху немає. Оскільки кожен масив std :: з різним розміром є різним типом, вам потрібно написати функцію, яка може працювати на різних типах. Отже, шаблони - це рішення для масиву std ::.
bstamour

4
Найкрасивіша частина використання шаблону тут полягає в тому, що ви можете зробити його ще більш загальним, щоб він працював з будь-яким контейнером послідовностей, а також зі стандартними масивами:template<typename C, typename M> void mulArray(C & arr, M multiplier) { /* same body */ }
Бенджамін Ліндлі,

1
@BenjaminLindley: Звичайно, це передбачає, що він взагалі може помістити код у заголовок.
Nicol Bolas,

27

Розмір arrayє частиною типу , тому ви не можете робити зовсім те, що хочете. Є кілька альтернатив.

Кращим буде взяти пару ітераторів:

template <typename Iter>
void mulArray(Iter first, Iter last, const int multiplier) {
    for(; first != last; ++first) {
        *first *= multiplier;
    }
}

Крім того, використовуйте vectorзамість масиву, який дозволяє зберігати розмір під час виконання, а не як частину його типу:

void mulArray(std::vector<int>& arr, const int multiplier) {
    for(auto& e : arr) {
        e *= multiplier;
    }
}

1
Я думаю, що це найкраще рішення; якщо ви збираєтеся зіткнутися з проблемою створення шаблону, зробіть його абсолютно загальним за допомогою ітераторів, які дозволять вам використовувати будь-який контейнер (масив, список, вектор, навіть вказівники старої школи C тощо) без мінусів. Дякую за підказку.
Марк Лаката

6

Я спробував нижче, і це просто спрацювало для мене.

#include <iostream>
#include <array>

using namespace std;

// made up example
void mulArray(auto &arr, const int multiplier) 
{
    for(auto& e : arr) 
    {
        e *= multiplier;
    }
}

void dispArray(auto &arr)
{
    for(auto& e : arr) 
    {
        std::cout << e << " ";
    }
    std::cout << endl;
}

int main()
{

    // lets imagine these being full of numbers
    std::array<int, 7> arr1 = {1, 2, 3, 4, 5, 6, 7};
    std::array<int, 6> arr2 = {2, 4, 6, 8, 10, 12};
    std::array<int, 9> arr3 = {1, 1, 1, 1, 1, 1, 1, 1, 1};

    dispArray(arr1);
    dispArray(arr2);
    dispArray(arr3);

    mulArray(arr1, 3);
    mulArray(arr2, 5);
    mulArray(arr3, 2);

    dispArray(arr1);
    dispArray(arr2);
    dispArray(arr3);

    return 0;
}

ВИХІД:

1 2 3 4 5 6 7

2 4 6 8 10 12

1 1 1 1 1 1 1 1 1 1

3 6 9 12 15 18 21

10 20 30 40 50 60

2 2 2 2 2 2 2 2 2 2


3
Це не дійсний C ++, а швидше розширення. Ці функції є шаблонами, навіть без template.
HolyBlackCat

1
Я вивчив це, і виявляється, що на auto foo(auto bar) { return bar * 2; }даний момент недійсний C ++, хоча він компілюється в GCC7 із встановленим прапором C ++ 17. З прочитаного тут , функціональні параметри, оголошені як auto, є частиною Concepts TS, яка в кінцевому підсумку повинна бути частиною C ++ 20.
Фібл

Попередження C26485
метабластер

5

РЕДАГУВАТИ

C ++ 20 попередньо включає std::span

https://en.cppreference.com/w/cpp/container/span

Оригінальна відповідь

Те, що ви хочете, - це щось подібне gsl::span, яке доступне у бібліотеці підтримки настанов, описаній у Основних рекомендаціях C ++:

https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#SS-views

Ви можете знайти реалізацію GSL із відкритим кодом лише для заголовків тут:

https://github.com/Microsoft/GSL

За допомогою цього gsl::spanви можете зробити це:

// made up example
void mulArray(gsl::span<int>& arr, const int multiplier) {
    for(auto& e : arr) {
        e *= multiplier;
    }
}

// lets imagine these being full of numbers
std::array<int, 17> arr1;
std::array<int, 6>  arr2;
std::array<int, 95> arr3;

mulArray(arr1, 3);
mulArray(arr2, 5);
mulArray(arr3, 2);

Проблема в std::arrayтому, що його розмір є частиною його типу, тому вам доведеться використовувати шаблон, щоб реалізувати функцію, яка приймаєstd::array довільний розмір.

gsl::spanз іншого боку, зберігає його розмір як інформацію про час виконання. Це дозволяє використовувати одну нешаблонну функцію для прийняття масиву довільного розміру. Він також приймає інші суміжні контейнери:

std::vector<int> vec = {1, 2, 3, 4};
int carr[] = {5, 6, 7, 8};

mulArray(vec, 6);
mulArray(carr, 7);

Дуже круто, так?


3

Безумовно, у C ++ 11 є простий спосіб написати функцію, яка приймає масив std :: відомого типу, але невідомого розміру.

Якщо ми не можемо передати функцію розміру масиву, то замість цього ми можемо передати адресу пам'яті, з якої починається масив, разом із 2-ю адресою, де масив закінчується. Пізніше, всередині функції, ми можемо використовувати ці 2 адреси пам'яті для обчислення розміру масиву!

#include <iostream>
#include <array>

// The function that can take a std::array of any size!
void mulArray(int* piStart, int* piLast, int multiplier){

     // Calculate the size of the array (how many values it holds)
     unsigned int uiArraySize = piLast - piStart;

     // print each value held in the array
     for (unsigned int uiCount = 0; uiCount < uiArraySize; uiCount++)     
          std::cout << *(piStart + uiCount) * multiplier << std::endl;
}

int main(){   

     // initialize an array that can can hold 5 values
     std::array<int, 5> iValues;

     iValues[0] = 5;
     iValues[1] = 10;
     iValues[2] = 1;
     iValues[3] = 2;
     iValues[4] = 4;

     // Provide a pointer to both the beginning and end addresses of 
     // the array.
     mulArray(iValues.begin(), iValues.end(), 2);

     return 0;
}

Вихід на консолі: 10, 20, 2, 4, 8


1

Це можна зробити, але для цього потрібно зробити кілька кроків. Спочатку напишіть a, template classщо представляє діапазон суміжних значень. Потім перешліть templateверсію, яка знає, наскільки велика arrayвона дляImpl версії, яка займає цей суміжний діапазон.

Нарешті, впровадити contig_rangeверсію. Зверніть увагу, що for( int& x: range )працює для contig_range, тому що я реалізував begin()і, end()а покажчики є ітераторами.

template<typename T>
struct contig_range {
  T* _begin, _end;
  contig_range( T* b, T* e ):_begin(b), _end(e) {}
  T const* begin() const { return _begin; }
  T const* end() const { return _end; }
  T* begin() { return _begin; }
  T* end() { return _end; }
  contig_range( contig_range const& ) = default;
  contig_range( contig_range && ) = default;
  contig_range():_begin(nullptr), _end(nullptr) {}

  // maybe block `operator=`?  contig_range follows reference semantics
  // and there really isn't a run time safe `operator=` for reference semantics on
  // a range when the RHS is of unknown width...
  // I guess I could make it follow pointer semantics and rebase?  Dunno
  // this being tricky, I am tempted to =delete operator=

  template<typename T, std::size_t N>
  contig_range( std::array<T, N>& arr ): _begin(&*std::begin(arr)), _end(&*std::end(arr)) {}
  template<typename T, std::size_t N>
  contig_range( T(&arr)[N] ): _begin(&*std::begin(arr)), _end(&*std::end(arr)) {}
  template<typename T, typename A>
  contig_range( std::vector<T, A>& arr ): _begin(&*std::begin(arr)), _end(&*std::end(arr)) {}
};

void mulArrayImpl( contig_range<int> arr, const int multiplier );

template<std::size_t N>
void mulArray( std::array<int, N>& arr, const int multiplier ) {
  mulArrayImpl( contig_range<int>(arr), multiplier );
}

(не перевірено, але дизайн повинен працювати).

Потім у вашому .cppфайлі:

void mulArrayImpl(contig_range<int> rng, const int multiplier) {
  for(auto& e : rng) {
    e *= multiplier;
  }
}

Це має мінус, що код, який циклічно переглядає вміст масиву, не знає (під час компіляції), наскільки великий масив, що може коштувати оптимізації. Ця перевага полягає в тому, що реалізація не повинна бути в заголовку.

Будьте обережні при явному побудуванні a contig_range, оскільки, якщо ви його передасте, a setвін буде вважати, що setдані є суміжними, що є хибним, і виконуватиме невизначену поведінку всюди. Єдиними двома stdконтейнерами, над якими це гарантовано працювати, є vectorі array(і масиви у стилі С, як це трапляється!). dequeнезважаючи на те, що довільний доступ не є суміжним (небезпечно, він суміжний невеликими шматками!), listнавіть не близько, а асоціативні (упорядковані та невпорядковані) контейнери однаково несуміжні.

Отже, три конструктори, які я реалізував, де std::array, std::vectorі масиви у стилі С, які в основному охоплюють основи.

Реалізація []також проста, і між цим for()і []є більшість того, що вам потрібно array, чи не так?


Хіба це не просто зміщення шаблону в інше місце?
GManNickG

@GManNickG свого роду. Заголовок отримує дійсно коротку templateфункцію, майже не містить деталей реалізації. ImplФункція не є templateфункцією, і тому ви можете успішно приховати реалізацію в .cppфайл за вашим вибором. Це дійсно грубий тип стирання типу, коли я витягую здатність перебирати суміжні контейнери в простіший клас, а потім передаю це через ... (хоча аргумент multArrayImplприймається templateяк аргумент, це не саме templateпо собі).
Якк - Адам Неврамонт

Я розумію, що цей клас подання масиву / масиву проксі-сервера іноді корисний. Моя пропозиція полягала б у передачі початку / кінця контейнера в конструктор, щоб вам не довелося писати конструктор для кожного контейнера. Крім того, я не писав би '& * std :: begin (arr)', оскільки тут не потрібно проводити посилання та брати адресу, оскільки std :: begin / std :: end вже повертає ітератор.
Ricky65

@ Ricky65 Якщо ви використовуєте ітератори, вам потрібно розкрити реалізацію. Якщо ви використовуєте покажчики, ви цього не робите. Посилання &*на ітератор (який не може бути вказівником), потім робить вказівник на адресу. Для суміжних даних пам’яті вказівник на beginта вказівник на один минулий endє також ітераторами довільного доступу, і вони однакові для кожного суміжного діапазону над типом T.
Якк - Адам Неврамонт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.