Ошибка с диапазоном для внутренней функции

У меня немного проблемы с диапазоном для С++. Я пытаюсь использовать его для отображения массива on и int (int []), и он отлично работает, когда я делаю это на главной функции, например, в:

int main(int argc, char const *argv[]) {

  int v[] = {3, 4, 6, 9, 2, 1};

  for (auto a : v) {
      std::cout << a << " ";
  }
  std::cout << std::endl;

  return 0;
}

Я получаю желаемый и ожидаемый результат:

3 4 6 9 2 1

Но ситуация становится немного странной, когда я пытаюсь использовать диапазон для функции внутри, в качестве примера у меня проблема с этим кодом:

void printList(int *v);

int main(int argc, char const *argv[]) {

  int v[] = {3, 4, 6, 9, 2, 1};

  printList(v);

  return 0;
}

void printList(int *v) {
  for (auto a : v) {
    std::cout << a << " ";
  }
  std::cout << std::endl;
}

Для меня это то же самое, что я делал внутри основного, а также с обычным для работы полностью. Странная ошибка выглядит следующим образом:

p4.cpp: In function ‘void printList(int*)’:
p4.cpp:15:17: error: ‘begin’ was not declared in this scope
   for (auto a : v) {
                 ^
p4.cpp:15:17: note: suggested alternative:
In file included from /usr/include/c++/5/string:51:0,
                 from /usr/include/c++/5/bits/locale_classes.h:40,
                 from /usr/include/c++/5/bits/ios_base.h:41,
                 from /usr/include/c++/5/ios:42,
                 from /usr/include/c++/5/ostream:38,
                 from /usr/include/c++/5/iostream:39,
                 from p4.cpp:1:
/usr/include/c++/5/bits/range_access.h:105:37: note:   ‘std::begin’
   template<typename _Tp> const _Tp* begin(const valarray<_Tp>&);
                                     ^
p4.cpp:15:17: error: ‘end’ was not declared in this scope
   for (auto a : v) {
                 ^
p4.cpp:15:17: note: suggested alternative:
In file included from /usr/include/c++/5/string:51:0,
                 from /usr/include/c++/5/bits/locale_classes.h:40,
                 from /usr/include/c++/5/bits/ios_base.h:41,
                 from /usr/include/c++/5/ios:42,
                 from /usr/include/c++/5/ostream:38,
                 from /usr/include/c++/5/iostream:39,
                 from p4.cpp:1:
/usr/include/c++/5/bits/range_access.h:107:37: note:   ‘std::end’
   template<typename _Tp> const _Tp* end(const valarray<_Tp>&);
                                     ^

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

template <std::size_t len>
void printList(int (&v)[len]) {
  for (int a : v) {
    std::cout << a << " ";
  }
  std::cout << std::endl;
}

Что работает отлично, но если я использую что-то вроде этого:

template <std::size_t len>
void printList(int (&v)[len]);

int main(int argc, char const *argv[]) {
           .........
}

void printList(int (&v)[len]) {
  for (int a : v) {
    std::cout << a << " ";
  }
  std::cout << std::endl;
}

Я получаю сообщение об ошибке:

p4.cpp:15:25: error: ‘len’ was not declared in this scope
 void printList(int (&v)[len]) {
                         ^
p4.cpp: In function ‘void printList(...)’:
p4.cpp:16:16: error: ‘v’ was not declared in this scope
   for (int a : v) {

Почему досуг случается? Есть ли простое решение без использования формата шаблона? Есть ли способ, которым я могу использовать аргумент как способ передать массив и информацию о неявном размере?

Ответы

Ответ 1

Диапазон, основанный на циклах, по сути является ничем иным, как синтаксическим сахаром, то есть извлекается из cppreference

for ( range_declaration : range_expression ) loop_statement

функционально эквивалентно следующему:

{
    auto && __range = range_expression ;
    for (auto __begin = begin_expr, __end = end_expr;
            __begin != __end; ++__begin) {
        range_declaration = *__begin;
        loop_statement
    }
}

где begin_expr и end_expr формируются следующим образом

1) Если выражение range_expression является выражением типа массива, тогда begin_expr является __range и end_expr (__range + __bound), где __bound - это количество элементов в массиве (если массив имеет неизвестный размер или имеет неполный тип, программа плохо сформирована)

2) Если выражение range_expression является выражением типа класса C, которое имеет член с именем begin и/или член с именем end (независимо от типа или доступности такого элемента), begin_expr является __range.begin() и end_expr __range.end();

3) В противном случае begin_expr запускается (__ диапазон), а end_expr - конец (__ диапазон), которые находятся через зависящий от аргумента поиск (не-ADL-поиск не выполняется).


Посмотрите, как это относится к вашему делу:

В первом случае v, безусловно, является выражением типа массива или, если быть точным, типа int(&)[6], поэтому мы используем case (1), где __bound = 6 и т.д. (для краткости опущены полные вычитаемые замены)

Во втором случае, когда у вас есть функция, v имеет тип int*, и поскольку он не является типом массива и не имеет указателя, члены по умолчанию имеют случай (3), который использует ADL, чтобы определить функцию для вызова begin(__range), которая не дает результата для типов указателей, поэтому компилятор жалуется на error: ‘begin’ was not declared in this scope.

В третьем случае вы сделали ошибку при попытке определить шаблон функции printList. Вы должны сохранить часть template<...>, включенную в объявление, иначе это просто определение функции. Вот почему компилятор сообщает вам error: ‘len’ was not declared in this scope. Таким образом, правильный и рабочий код

template <std::size_t len>
void printList(int (&v)[len]);

int main(int argc, char const *argv[]) {
  int v[] = {3, 4, 6, 9, 2, 1};
  printList(v);
  return 0;
}

template <std::size_t len>
void printList(int (&v)[len]) {
  for (int a : v) {
    std::cout << a << " ";
  }
  std::cout << std::endl;
}

В других ответах уже предлагается использовать другой тип контейнера, например std::array<int, 6>, чтобы повысить допустимую точку. Определенно взгляните на них, особенно с инициализацией скобок, которую вы можете обновить до них практически без затрат.

Ответ 2

"причина, по которой я думаю, что это может произойти, так как я указательное представление массива некоторая информация теряется, но почему эта информация потеряна, я не знаю".

Да. Массивы легко распадаются на указатели, а указатель не знает длины массива. Диапазон, основанный на цикле, необходим для оценки begin() и end() из типа данных.
Мое предложение состоит в том, чтобы избежать массивов стилей C и вместо этого использовать std::array:

#include <iostream>
#include <array>

void printList(std::array<int,6> const& v);

int main(int argc, char const *argv[]) {

  std::array<int,6> v{{3, 4, 6, 9, 2, 1}};
  printList(v);

  return 0;
}

void printList(std::array<int,6> const& v) {
  for (auto a : v) {
    std::cout << a << " ";
  }
  std::cout << std::endl;
}

Ответ 3

Существует большая разница между массивом и указателем.

Вы можете выполнять итерацию массива с использованием range-based, поскольку размер массива известен во время компиляции. Однако то, что вы передаете функции - это указатель на первый элемент массива. Размер не известен на этом шаге, поэтому сбой на основе диапазона.

В вашем втором примере с шаблоном, уловка, вы забыли template <std::size_t len> в определении printList, так что у вас есть две разные функции, шаблонные и нетемплированные, которые вызывается.

В этом точном случае - я бы рекомендовал использовать более современные std::array

Ответ 4

Такие циклы for используют функции-члены begin и end, чтобы определить, где начало и где конец последовательности.

Например, std::vector имеет эти функции и, таким образом, поддерживает такую ​​итерацию. Указатели фактически представляют собой целые числа, которые представляют адреса памяти и не имеют этих функций, что делает невозможным итерацию по указателю (что само по себе не имеет смысла).

Вместо этого вы можете сделать итерацию:

void printList(int *begin, int *end) {
    for(; begin < end; ++begin)
        std::cout << *begin;
    std::cout << std::endl;
}

Это работает внутри main в вашем случае, потому что массивы имеют begin и end, поскольку размер массива известен. Однако передача массива в функцию заставляет его распадаться на указатель.

Ответ 5

При передаче массива в функцию он распадается на указатель, поэтому он теряет способность использоваться с std:: begin и std:: end. Современный способ сделать то, что вы хотите, это использовать std:: array (вы не должны использовать C-образные массивы на С++, если это возможно):

#include <iostream>
#include <array>

template <typename T, size_t ArraySize>
void printList(const std::array<T, ArraySize>& v)
{
    for (auto a : v) {
        std::cout << a << " ";
    }
    std::cout << std::endl;
}

int main(int argc, char const *argv[]) {

  std::array<int,6> v = {3, 4, 6, 9, 2, 1};

  printList(v);

  return 0;
}    

Ответ 6

Массив внутри main имеет известный размер.

После перехода к функции printList он заглох на указатель на int s, следовательно, вы получите ошибки.

Ответ 7

Вы можете передать массив фиксированного размера функции:

void printList(int (&v)[6]) {        // pass by reference
    for (auto& a : v) {              // use reference to avoid making a copy
        std::cout << a << " ";
    }
}

Однако, конечно, мы не хотим писать функцию, которая работает только для массивов определенного размера. Здесь имеет смысл использовать шаблон:

template <int size>
void printList(int (&v)[size]) {
    for (auto& a : v) {
        std::cout << a << " ";
    }
}

Приятно то, что когда вы вызываете функцию, вы даже не замечаете, что это шаблон, потому что компилятор может вывести параметр шаблона (он знает размер, конечно):

int main() {
    int v[] = {3, 4, 6, 9, 2, 1};
    printList(v); 
}

печатает:

3 4 6 9 2 1

Ответ 8

int v[] = {3, 4, 6, 9, 2, 1};

получил тип int[6], а int *v.. ну его тип int *. Вы можете использовать указатель на int для доступа к массиву, но он не несет информацию о размере этого массива. Вы можете передать массив, как это, но вы ограничите себя размером массива:

void foo(int (&p)[6])

или создать шаблон:

template <std::size_t size> void foo( int (&p)[size] )

Если по какой-то причине вы не можете использовать automic для() циклов (например, для переносимости на платформы, где поддержка С++ 11\14 сомнительна), вам нужно использовать либо std:: array\ std::vector, либо передать указатель и размер массива