Есть ли способ определить переменные двух разных типов в инициализаторе цикла for? - PullRequest
6 голосов
/ 15 мая 2009

Вы можете определить 2 переменные одного типа в цикле for:

int main() {
  for (int i = 0, j = 0; i < 10; i += 1, j = 2*i) {
    cout << j << endl;
  }
}

Но нельзя определять переменные разных типов:

int main() {
  for (int i = 0, float j = 0.0; i < 10; i += 1, j = 2*i) {
    cout << j << endl;
  }
}

Есть ли способ сделать это? (Мне не нужно использовать i внутри цикла, просто j.)

Если у вас есть полностью взломанное и неясное решение, это нормально для меня.

В этом надуманном примере я знаю, что вы можете просто использовать double для обеих переменных. Я ищу общий ответ.

Пожалуйста, не предлагайте перемещать какие-либо переменные за пределы для тела, вероятно, для меня это невозможно, поскольку один из них - итератор, который должен исчезнуть сразу после цикла, а оператор for должен быть включен в мой макрос foreach :

#define foreach(var, iter, instr) {                  \
    typeof(iter) var##IT = iter;                     \
    typeof(iter)::Element var = *var##IT;            \
    for (; var##_iterIT.is_still_ok(); ++var##IT, var = *var#IT) {  \
      instr;                                         \
    }                                                \
  }

Может использоваться таким образом:

foreach(ii, collection, {
  cout << ii;
}). 

Но мне нужно что-то, что будет использоваться так:

foreach(ii, collection)
  cout << ii;

Пожалуйста, не вносите никаких накладных расходов во время выполнения (но это может быть медленным при компиляции).

Ответы [ 13 ]

25 голосов
/ 15 мая 2009

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

Вы можете сделать это:

#include <iostream>

int main( int, char *[] ) {
    {
        float j = 0.0;

        for ( int i = 0; i < 10; i += 1, j = 2*i ) {
            std::cout << j << std::endl;
        }
    }

    float j = 2.0; // works

    std::cout << j << std::endl;

    return 0;
}
11 голосов
/ 15 мая 2009

Ну, это некрасиво. Но вы можете использовать пару.

int main() {
  for (std::pair<int,float> p(0,0.0f); 
       p.first < 10; 
       p.first += 1, p.second = 2*p.first) {
    cout << p.second << endl;
  }
}
10 голосов
/ 15 мая 2009

Вот версия, использующая препроцессор boost (это просто для удовольствия. Реальный ответ см. На @ kitchen выше):

FOR((int i = 0)(int j = 0.0), i < 10, (i += 1, j = 2 * i)) { 

}

В первой части указана последовательность объявлений: (a)(b).... Переменные, объявленные позже, могут ссылаться на переменные, объявленные перед ними. Вторая и третья части как обычно. Если запятые встречаются во второй и третьей частях, можно использовать круглые скобки, чтобы они не разделяли аргументы макроса.

Есть два известных мне трюка, которые используются для объявления переменных, которые позже будут видны в составном операторе, добавленном вне макроса. Первый использует условия, например if:

if(int k = 0) ; else COMPOUND_STATEMENT

Тогда k виден. Естественно, его всегда нужно оценивать до false. Так что это не может быть использовано нами. Другой контекст такой:

for(int k = 0; ...; ...) COMPOUND_STATEMENT

Это то, что я собираюсь использовать здесь. Нам нужно будет наблюдать только одну итерацию COMPOUND_STATEMENT. Фактический цикл for, выполняющий проверку инкремента и условия, должен завершаться, поэтому к нему присоединяется добавленный составной оператор.

#include <boost/preprocessor.hpp>
#include <iostream>

#define EMIT_DEC_(R,D,DEC) \
    for(DEC; !_k; ) 

#define FOR(DECS, COND, INC) \
    if(bool _k = false) ; else \
      BOOST_PP_SEQ_FOR_EACH(EMIT_DEC_, DECS, DECS) \
        for(_k = true; COND; INC)

int main() {
    FOR((int i = 0)(float j = 0.0f), i < 10, (i += 1, j = 2 * i)) {
        std::cout << j << std::endl;
    }
}

Он создает кучу for операторов, каждое из которых вложено в другое. Расширяется в:

if(bool _k = false) ; else
  for(int i = 0; !_k; )
    for(float j = 0.0f; !_k; )
      for(_k = true; i < 10; (i += 1, j = 2 * i)) {
        std::cout << j << std::endl;
      }
8 голосов
/ 15 мая 2009
{
  int i = 0;
  float j = 0.0;
  for ( ; i < 10; i += 1, j = 2*i) {
    cout << j << endl;
  }
}

Переменные «исчезают» после блока.

7 голосов
/ 15 мая 2009

Это сделает итератор (или в данном случае float) исчезающим, когда он больше не нужен:

int main() {
  // some code...

  {
    float j = 0.0;
    for (int i = 0; i < 10; i += 1, j = 2*i) {
      cout << j << endl;
    }
  }

  // more code...
}
6 голосов
/ 15 мая 2009

Если у вас проблемы с макросами, есть стандартный do..while трюк, который отлично работает:

#define MYFOR(init, test, post, body) \
    do \
    { \
        init \
        for( ; test; post) \
            body \
    } while(0)

Используйте его следующим образом:

MYFOR( int i = 0; float j = 0.0f; , i < 10 , (i += 1, j = 2.0f * i),
    {
         cout << j << endl;
    } );

Это некрасиво, но делает то, что вы хотите: область действия i и j ограничена циклом do..while из макроса, и в конце требуется точка с запятой, так что вы не получите укусить, поместив его в предикат оператора if / else.

5 голосов
/ 20 мая 2009

Это тоже некрасиво, но предоставляет также некоторый общий способ объявления нескольких переменных с некоторыми заданными именем и типами в цикле for

int main() {
  for (struct { int i; float j; } x = { };
       x.i < 10; x.i += 1, x.j = 2 * x.i) {
    cout << x.j << endl;
  }
}
4 голосов
/ 15 мая 2009

РЕДАКТИРОВАТЬ : Вопрос изменился еще раз. Теперь вопрос явно хочет реализовать цикл foreach. Самый простой ответ:

#include <boost/foreach.hpp>
void( std::vector<int>& v ) {
   BOOST_FOREACH( int & x, v ) {
      x = x*2;
   }
}

Вставка переменной в блок кода

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

Есть несколько мест, где вы можете определить переменную с разными областями действия. Вы можете определить переменную внутри любого блока кода, и ее срок службы будет до конца этого конкретного блока. Вы можете определить переменную в скобках цикла for, а область действия будет блоком цикла. Вы также можете определить переменную внутри блока if, и ее область будет такой же, как у if (включая предложение else).

Вы можете комбинировать эти опции выше, чтобы создавать внешние и вставлять переменные в блок кода, не создавая переменную, срок жизни которой превышает срок жизни блока. Практическим примером будет определение цикла foreach (упрощенного для работы только с контейнерами STL. Синтаксис вызова будет:

void f( std::vector<int>& container ) 
{
   INTVECTOR_FOREACH( int & x, container )
   {
      x = x*2;
   }
}

С семантикой, аналогичной foreach в других языках: x получает ссылку на каждый элемент в контейнере, так что функция фактически удваивает каждое значение внутри целочисленного вектора.

Теперь код упрощенного макроса:

#define INTVECTOR_FOREACH( variable, container ) \
   for ( std::vector<int>::iterator it = container.begin(); it!=container.end(); ++it ) \
      if ( bool condition=false ) {} else \
         for ( variable = *it; !condition; condition=true )

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

Внешний для выполняет итерации по контейнеру, в каждой итерации мы выполняем еще одну для только один раз, определяя итерационную переменную (int & x в примере кода). Нам нужно условие для контроля количества итераций (1) внутреннего цикла, и это условие вводится с , если . Мы решаем сделать сбой if, чтобы гарантировать, что пользователь не получит неожиданных результатов, если он пишет else после цикла ... макросы хитры.

3 голосов
/ 15 мая 2009

Пожалуйста, не предлагайте перемещать какие-либо переменные за пределы для body, вероятно, для меня это невозможно, поскольку итератор должен исчезнуть сразу после цикла.

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

int main() 
{
  {
    float j = 0.0;
    for (int i = 0; i < 10; i += 1, j = 2*i) 
    {
      cout << j << endl;
    }
  }
  // more code...
}

Таким образом j выйдет из области видимости сразу после цикла.

2 голосов
/ 15 мая 2009

С учетом требований, которые вы даете, простейший код, который я могу себе представить, это:

for ( int i = 0; i < 10; ++i )
{
   float f = i * 2;
   std::cout << f << std::endl;
}

Вы используете только f как двойное значение i . Время жизни ограничено циклом, и (по крайней мере, в указанном вами упрощенном вопросе) плавающие элементы дешевы в создании (точно так же дешево, как и в назначении).

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

...