Есть ли простой способ итерации по статическому списку строк в C ++ - PullRequest
3 голосов
/ 22 мая 2009

Часто случается, что мне нужно перебирать список строк в моем коде C ++.

На таких языках, как Perl, это легко:

foreach my $x ("abc", "xyz", "123") {.... }

В прошлом это то, что я делал в C ++

const char* strs[] = { "abc", "xyz", "123" };
for (int i=0; i<sizeof(strs)/sizeof(const char*); i++) {
   const char *str = strs[i];
   ...

Если у меня уже есть контейнер STL, я могу использовать BOOST_FOREACH

std::vector<std::string> strs;
BOOST_FOREACH(std::string str, strs) {
   ...

Я пытался создать макрос для объединения всех этих концепций, но безуспешно.

Я бы хотел написать код, подобный следующему:

SPECIAL_STRING_FOREACH(const char* str, {"abc", "xyz", "123"}) {
   ...
}

Конечно, кто-то готовил это раньше.

Ответы [ 8 ]

9 голосов
/ 23 мая 2009

Вот моя попытка. К сожалению, он опирается на макросы variadic, которые являются функцией C99 / C ++ 1x. Но работает в GCC.

#include <boost/foreach.hpp>
#include <boost/type_traits.hpp>
#include <iostream>

#define SEQ_FOR_EACH(D, ...)                                        \
    if(bool c = false) ; else                                       \
        for(boost::remove_reference<boost::function_traits<void(D)> \
                ::arg1_type>::type _t[] = __VA_ARGS__;              \
            !c; c = true)                                           \
            BOOST_FOREACH(D, _t)

int main() {
    SEQ_FOR_EACH(std::string &v, { "hello", "doctor" }) {
        std::cout << v << std::endl;
    }
}

Обратите внимание, что вы также можете перебирать ссылочную переменную, чтобы избежать бесполезного копирования. Вот пример использования boost.preprocessor и синтаксиса (a)(b)..., компиляция до одного и того же кода после этапа предварительной обработки.

#define SEQ_FOR_EACH(D, SEQ)                                          \
    if(bool c = false) ; else                                         \
        for(boost::remove_reference<boost::function_traits<void(D)>   \
                ::arg1_type>::type _t[] = { BOOST_PP_SEQ_ENUM(SEQ) }; \
            !c; c = true)                                             \
            BOOST_FOREACH(D, _t)

int main() {
    SEQ_FOR_EACH(std::string &v, ("hello")("doctor")) {
        std::cout << v << std::endl;
    }
}

Хитрость заключается в том, чтобы собрать тип функции, который имеет в качестве параметра переменную перечисления, и получить тип этого параметра. Тогда boost::remove_reference удалит любую ссылку. Первая используемая версия boost::decay. Но это также преобразовало бы массивы в указатели, которые я нашел не то, что иногда нужно. Полученный тип затем используется как тип элемента массива.

Для использования в шаблонах, где переменная перечислителя имеет зависимый тип, вам придется использовать другой макрос, который ставит typename перед boost::remove_reference и boost::function_traits. Может назвать это SEQ_FOR_EACH_D (D == в зависимости).

6 голосов
/ 22 мая 2009

Обратите внимание, что работать с массивом C строк проще, если вы отметите конец массива:

const char* strs[] = { "abc", "xyz", "123", NULL };
for (int i=0; strs[i] != NULL  i++) {
  ...
}
4 голосов
/ 22 мая 2009

Примерно так:


void func( const char* s ) { /* ... */ }

const char* array[] = { "abc", "xyz", "123" };
std::for_each( array, array + 3, func );

Возможно, вы также захотите взглянуть на boost::array.

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

Cou может использовать хак va_arg для создания функции, которая возвращает итеративную коллекцию (обратите внимание, что это действительно хак!)

Новые стандарты C ++ (C ++ 0x) обеспечат более удобный способ инициализации ( списки инициализаторов )

Другая возможность - использовать boost::assignment в сочетании с FOREACH.

Обратите внимание, что BOOST::FOREACH применимо и к массивам!

1 голос
/ 22 мая 2009

Здесь помогает создание макроса, который возвращает размер массива.

#define N_ELEMS(a) (sizeof(a) / sizeof((a)[0]))

Тогда ваш оригинальный код выглядит не так уж плохо.

for(int i = 0; i < N_ELEMS(strs); ++i) {
  ...
}

Это хорошая идиома для перебора любого статического массива, а не только массивов строк.

0 голосов
/ 23 мая 2009

Я бы попробовал просто использовать BOOST_FOREACH для массива напрямую. документация , кажется, думает, что это будет работать.

На самом деле это так. Это должно быть две строки, но это работает:

const char * myarray[] = {"abc", "xyz", "123"};
BOOST_FOREACH (const char *str, myarray) {
  std::cout << "Hello " << str << std::endl;
}
0 голосов
/ 22 мая 2009

Это должно сделать это (я не проверял, так что могут быть некоторые опечатки)

#define STR_ARRAY_FOREACH(I) const char* Items[] = I; for( const char *item = Items[0], *end = strs[ sizeof( Items ) / sizeof( const char* ) ]; item < end; ++item )

Затем используйте элемент в цикле:

STR_ARRAY_FOREACH({ "abc", "xyz", "123" })
{
    cout << item << "\n";
}
0 голосов
/ 22 мая 2009

Вместо этого:

for (int i=0; i<sizeof(strs)/sizeof(const char*); i++)
{
    const char *str = strs[i];
    ...

Учтите это:

for (const char *ptr = strs[0],
    *end = strs[sizeof(strs)/sizeof(const char*)];
    ptr < end; ++ptr)
{
    ...

Вам может показаться, что эту форму легче макроризировать; в любом случае переменная ptr имитирует итератор.

Конечно, кто-то готовил это раньше.

Я сомневаюсь, что они должны. Цикл for идиоматичен и прост для чтения (особенно если вы знаете размер массива), пользовательские макросы нестандартны.

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