#include <iostream>
#include <boost/range/iterator_range.hpp>
int main()
{
const int array[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Just process the first element separately. No checks required in the loop.
std::cout << *std::begin(array);
for (const auto& item : boost::make_iterator_range(array, 1, 0))
{
std::cout << ", " << item;
}
}
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, rg45, Вы писали:
M>>Для нового range-for'а есть какой-нибудь вариант?
R>При помощи boost::make_iterator_range можно вырезать подпоследовательности из последовательностей самых разных типов (в т.ч. и встроенных массивов):
Только встроенные средства
R>P.S. Сложноватым пример показался? Я упростил немного.
???
R>
R>#include <iostream>
R>#include <boost/range/iterator_range.hpp>
R>int main()
R>{
R> const int array[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
R> // Just process the first element separately. No checks required in the loop.
R> std::cout << *std::begin(array);
R> for (const auto& item : boost::make_iterator_range(array, 1, 0))
R> {
R> std::cout << ", " << item;
R> }
R>}
R>
Как минимум, этот код не будет корректно работать для пустого контейнера.
M>>Как минимум, этот код не будет корректно работать для пустого контейнера.
R>Все верно. Потому что этот код максимально упрощен. Если хочется универсальности, смотри первоначальный ("сложный") вариант:
Я бы заменил, все-таки, "идеальный" на "подходящий для большинства типовых случаев". Во-первых потому, что этот вариант не универсален — он не подходит для случаев сгенерированных или трансформировнных последовательностей, где разымерование итераторов возвращает rvalue. Также он не подходит для lvalue-последовательностей, где один и тот же елемент может встретиться более одного раза. Использовать этот подход в качестве максимально общего не получится никак. А ты говоришь "идеальный".
А во-вторых, внесение внутрь цикла операции, которую реально нужно выпольнить только один раз, как бы это помягче назвать... Непрофессионализм, так скажем. И дело тут совсем не в быстродействии.
--
Не можешь достичь желаемого — пожелай достигнутого.
R>Я бы заменил, все-таки, "идеальный" на "подходящий для большинства типовых случаев".
Ну я же сказал "практически идеальный"
R>Во-первых потому, что этот вариант не универсален — о не подходит для случаев. сгенерированных или трансформировнных последовательностей, где разымерование итераторов возвращает rvalue.
Для стандартных контейнеров подходит — и хорошо.
R>Также он не подходит для lvalue-последовательностей, где один и тот же елемент может встретиться более одного раза.
Можно пример такого?
R>А во-вторых, выполнение в цикле операции, которую реально нужно выпольнить только один раз, как бы это помягче назвать... Непрофессионализм, так скажем.
Не согласен. Во-первых, этот код возможно будет оптимизирован компилятором. Во-вторых, если первое не сработает, то весьма вероятно, что предсказатель процессора его вычислит. В-третьих, если это критично, то его всегда можно переписать "правильно". В четвертых, я такую конструкцию обычно использую при выводе в поток — в лог, и тп. А ввод-вывод, а тем более плюсовые потоки — это тормоза еще те, и беспокоиться о таких мелочах — это как экономить на спичках
Но самое главное — это читабельность. В твоем варианте она хуже. Я бы сказал, что твой вариант — это случай преждевременной оптимизации.
R>>Также он не подходит для lvalue-последовательностей, где один и тот же елемент может встретиться более одного раза.
M>Можно пример такого?
Пожалуйста. В этом примере ты проходишь по последовательности из десяти элементов и получаещь, что каждый элемент является и первым, и последним одновременно.
Только не придирайся, пожалуйта, к использованияю boost::adaptors::indirected — он использован исключительно ради упрощения примера. Использование адаптеров контейнеров давно уже не является прерогативой буста и пишется такой адаттор за считанные минуты в несколько строк.
#include <iostream>
#include <vector>
#include <boost/range/adaptor/indirected.hpp>
int main()
{
std::cout << std::boolalpha;
int t = 42;
std::vector<const int*> v(10, &t);
const auto cont = v | boost::adaptors::indirected;
for (auto& a : cont)
{
auto first = &a == &cont.front();
auto last = &a == &cont.back();
std::cout << first << ", " << last << std::endl;
}
}
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, rg45, Вы писали:
R>>>Также он не подходит для lvalue-последовательностей, где один и тот же елемент может встретиться более одного раза.
M>>Можно пример такого?
R>Пожалуйста. В этом примере ты проходишь по последовательности из десяти элементов и получаещь, что каждый элемент является и первым, и последним одновременно.
Ок.
R>Только не придирайся, пожалуйта, к использованияю boost::adaptors::indirected — он использован исключительно ради упрощения примера.
Хорошо, не буду
Да, надо знать, что когда можно, а когда нельзя. Шаблонную функцию для произвольного контейнера я не стал бы так писать, а если по месту для какого-нибудь вектора надо по-быстренькому — то не вижу проблем
R>А во-вторых, выполнение в цикле операции, которую реально нужно выпольнить только один раз, как бы это помягче назвать... Непрофессионализм, так скажем. И дело тут совсем не в быстродействии.
M>Не согласен. Во-первых, этот код возможно будет оптимизирован компилятором. Во-вторых, если первое не сработает, то весьма вероятно, что предсказатель процессора его вычислит. В-третьих, если это критично, то его всегда можно переписать "правильно". В четвертых, я такую конструкцию обычно использую при выводе в поток — в лог, и тп. А ввод-вывод, а тем более плюсовые потоки — это тормоза еще те, и беспокоиться о таких мелочах — это как экономить на спичках
Однако. Вырезал слова "и дело тут совсем не в быстродействии" и начал отстаивать свою позицию именно с точки зрения быстродействия
M>Но самое главное — это читабельность. В твоем варианте она хуже.
Я не считаю, что внесение внутрь цикла кода, который заведомо должен выполниться только один раз, как-то улучшает читабельность. Все как раз наоборот.
M>Я бы сказал, что твой вариант — это случай преждевременной оптимизации.
Ну ты для начала предоставь РАВНОЦЕННЫЙ вариант, потом можно будет сравнить. А пока говорить-то не о чем.
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, Marty, Вы писали:
M>Да, надо знать, что когда можно, а когда нельзя. Шаблонную функцию для произвольного контейнера я не стал бы так писать, а если по месту для какого-нибудь вектора надо по-быстренькому — то не вижу проблем
Ну собственно об этом я и говорил — слово "идеальный" как-то не очень уместно в этом случае
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, rg45, Вы писали:
R>Однако. Вырезал слова "и дело тут совсем не в быстродействии" и начал отстаивать свою позицию именно с точки зрения быстродействия
Сорян, я не специально. Просто просмотрел
M>>Но самое главное — это читабельность. В твоем варианте она хуже.
R>Я не считаю, что внесение внутрь цикла кода, который заведомо должен выполниться только один раз, как-то улучшает читабельность. Все как раз наоборот.
#include <iostream>
#include <vector>
int main()
{
std::vector<int> vec(10, 5);
for( const auto &v : vec )
{
if (&v!=!vec.front()) std::cout<<", ";
std::cout<<v;
}
}
M>>Я бы сказал, что твой вариант — это случай преждевременной оптимизации.
R>Ну ты для начала предоставь РАВНОЦЕННЫЙ вариант, потом можно будет сравнить. А пока говорить-то не о чем.
Не кипятись
Ну, и для начала — почему именно равноценный? Я же сказал, что в простых известных случаях предложенный Went'ом вариант проще и удобней в использовании.
Здравствуйте, Marty, Вы писали: M>>>Я бы сказал, что твой вариант — это случай преждевременной оптимизации. R>>Ну ты для начала предоставь РАВНОЦЕННЫЙ вариант, потом можно будет сравнить. А пока говорить-то не о чем. M>Ну, и для начала — почему именно равноценный? Я же сказал, что в простых известных случаях предложенный Went'ом вариант проще и удобней в использовании.
Ну потому, что когда ты это сделаешь, ты поймешь, что мой код так написан именно из соображений лучшей структуры кода и читабельности, а вовсе не ради оптимизации. Те же варианты, которые ты до сих пор предлагал, не релевантны для такого сравнения. M>ЗЫ Кстати, а зачем буст?
Затем, что в той версии gcc, что в ideone, этих функций просто еще нету. Требование "чтоб без буста" ты ведь позже выдвинул. M>Чем плох такой вариант:
Да ни чем не хуже. Только зачем ты этот топик создавал, если вариант обычного for тебя тоже устраивает, оказывается
Здравствуйте, rg45, Вы писали:
M>>Да, надо знать, что когда можно, а когда нельзя. Шаблонную функцию для произвольного контейнера я не стал бы так писать, а если по месту для какого-нибудь вектора надо по-быстренькому — то не вижу проблем
R>Ну собственно об этом я и говорил — слово "идеальный" как-то не очень уместно в этом случае
При этом сам вырезаешь из: "практически идеальный"
Здравствуйте, rg45, Вы писали:
M>>ЗЫ Кстати, а зачем буст?
R>Затем, что в той версии gcc, что в ideone, этих функций просто еще нету. Требование "чтоб без буста" ты ведь позже ввел.
Ну, вообще я интересовался языковыми средствами C++ из новых стандартов, и думал, что, возможно, предусмотрено какое-то языковое средство для определения крайних случаев.
M>>Чем плох такой вариант:
R>Да ни чем не хуже. Только зачем ты этот топик создавал, если вариант обычного for тебя тоже устраивает, оказывается
Да если столько писанины в итоге, то уже как-то и не важно, в каком виде этот несчастный for будет
M>>Да если столько писанины в итоге, то уже как-то и не важно, в каком виде этот несчастный for будет
R>Ну смотри, я тут походу пьессы уже предоставил два варианта — один максимально универсальный
Здравствуйте, rg45, Вы писали:
R>>>Да не вопрос, давай упростим, но только оба варианта!
M>>Нормас!
R>Не, ты скажи, что я тебя убедил
Неа
1) Таки буст.
2) Таки то, что с пустым вектором не работает. Это таки критичнее, чем то, что мой вариант не работает с произвольным контейнеромадаптером
Здравствуйте, Marty, Вы писали:
M>2) Таки то, что с пустым вектором не работает. Это таки критичнее, чем то, что мой вариант не работает с произвольным контейнеромадаптером
Ай, блин, я ждал, что ты это скажешь!
Лады, замечание справедливое, подчиняюсь.
if (!cont.empty())
{
std::cout << cont.front();
for (auto& a : boost::make_iterator_range(cont, 1, 0))
std::cout << ", " << a;
}
for (auto& a : cont)
{
if (&a != &cont.front())
std::cout << ", ";
std::cout << item;
}
По компактности соизмеримо. По надежности, и универсальности мой вариант лучше, ты вроде бы признаешь это. Но вот по читабельности мне мой вариант нравится больше: сразу видно, что первый элемент обрабатывается иначе, чем хвост — логика программы легче отслеживается.
Ну вот только буст остается. Но я лично против буста ничего не имею.
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, rg45, Вы писали:
M>>2) Таки то, что с пустым вектором не работает. Это таки критичнее, чем то, что мой вариант не работает с произвольным контейнеромадаптером
R>Ай, блин, я ждал, что ты это скажешь!
На самом деле, сначала не очень хотелось занудствовать, но я опасался обвинений в вопиющем непрофессионализме, и решил таки показать, что немного таки разбираюсь в уровнях допустимости тех или иных решений, хоть возможно и не слишком хорошо
R>По компактности соизмеримо. По надежности, и универсальности мой вариант лучше, ты вроде бы признаешь это.
Согласен.
R>Но вот по читабельности мне мой вариант нравится больше: сразу видно, что первый элемент обрабатывается иначе, чем хвост — логика программы легче отслеживается.
Имхо — вкусовщина и дело привычки
R>Ну вот только буст остается. Но я лично против буста ничего не имею.
По возможности избегаю. Сейчас вообще продакшн код пишу для STMки, а там у кейла такая ситуёвина, что компилятор вроде допилили до 11го стандарта, а библиотеки — забыли, поленились, или бабла на это зажали. Это вроде даже не кейловская проблема, а армовская. Буст втуда вкорячивать не вижу особого смысла
С другой стороны, иногда пишу всякие утилитки для локального вспоможения на MSVC, C++17, а код, по возможности, для одних и тех же случаев стараюсь переиспользовать и там и там. Более новые стандарты пробую использовать только там, что в STMку точно не пролезет. А с нынешним достаточно резвым темпом принятия фич в стандарт буст таскать ради пары мелочей смысла не вижу
Здравствуйте, B0FEE664, Вы писали:
M>>Для нового range-for'а есть какой-нибудь вариант? BFE>Есть вот такой модный вариант, но он не то, что вы просите: BFE>
BFE> if ( auto it = std::begin(v), itEnd = std::end(v); itEnd != it )
BFE> {
BFE> std::cout << *it;
BFE> for(++it; itEnd != it; ++it)
BFE> {
BFE> std::cout << ", " << *it;
BFE> }
BFE> }
BFE>
Вот эта многоэтажность в условии глаз не радует совершенно. Зачем вообще здесь что-то выдумывать, когда все предельно просто:
if (!std::empty(v))
{
std::cout << std::front(v);
for(auto it = std::begin(v) + 1; it != std::end(v); ++it)
{
std::cout << ", " << *it;
}
}
Ну правда std::empty и std::front появляются только в С++17. Но их отсутствие легко восполняется, могу показать, если нужно.
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, rg45, Вы писали:
R>Вот эта многоэтажность в условии глаз не радует совершенно.
Я пробовал так:
if ( auto [it, itEnd] = {std::begin(v), std::end(v)}; itEnd != it )
но не собралось.
R>Зачем вообще здесь что-то выдумывать, когда все предельно просто:
Затем, чтобы избежать вызова std::end(v) на каждую проверку условия цикла.
Здравствуйте, B0FEE664, Вы писали:
BFE>Здравствуйте, rg45, Вы писали:
R>>Вот эта многоэтажность в условии глаз не радует совершенно. BFE>Я пробовал так: BFE>
BFE>if ( auto [it, itEnd] = {std::begin(v), std::end(v)}; itEnd != it )
BFE>
Здравствуйте, B0FEE664, Вы писали:
R>>Зачем вообще здесь что-то выдумывать, когда все предельно просто: BFE>Затем, чтобы избежать вызова std::end(v) на каждую проверку условия цикла.
Меня тут давеча в преждевременной оптимизации упрекали. Так вот это оно самое. Ты наворачиваешь код, борясь с вымышленным импактом.
Не будет там никакого ВЫЗОВА — в подавляющем-подавляющем большинстве случаев. А тех редких случаях, когда будет вызов — не факт, что именно он окажется узким горлышком. Ну и наконец, если при каком-то невероятнейшем раскладе это случится, то оптимизация будет тривиальной. Я только не понимаю, зачем нам сейчас рассматривать эту экзотику, хогда разговор на уровне "что такое хорошо и что такое плохо".
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, B0FEE664, Вы писали:
BFE>Дело не в этом. BFE>Вот такая конструкция не работает: BFE>
BFE> int i1 = 1;
BFE> int i2 = 2;
BFE> auto [a, b] = std::tuple{i1, i2};
BFE>
BFE>А жаль.
Ибо потому что через тупл надо, потому что тут присваивание, а значит {} описывает initializer_list вместо конструктора. Были бы разные типу у it и i2, то даже до этого не дошел бы.
Здравствуйте, rg45, Вы писали:
BFE>>Затем, чтобы избежать вызова std::end(v) на каждую проверку условия цикла. R>Меня тут давеча в преждевременной оптимизации упрекали.
Это был не я.
R>Так вот это оно самое.
Да неужели? А писать ++it вместо it++ — тоже преждевременная оптимизация?
Здравствуйте, B0FEE664, Вы писали:
R>>Так вот это оно самое. BFE>Да неужели?
Да точно-точно.
BFE>А писать ++it вместо it++ — тоже преждевременная оптимизация?
Нет. Но писать it++ вместо ++it — это преждевременная пессимизация. Зачем писать более сложное выражение, когда можно написать более простое при прочих равных условиях? Подробнее здесь: https://doc.lagout.org/programmation/C/CPP101.pdf, рекомендация №9: "Don't pessimize prematurely".
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, andrey.desman, Вы писали:
AD>Ибо потому что через тупл надо, потому что тут присваивание, а значит {} описывает initializer_list вместо конструктора. Были бы разные типу у it и i2, то даже до этого не дошел бы.
А откуда тут initializer_list ? Зачем он тут?
struct A
{
int n;
char* s;
};
A a = {1, nullptr};
А если убрать присваивание, то почему не работает?
Здравствуйте, B0FEE664, Вы писали:
BFE>Здравствуйте, andrey.desman, Вы писали:
AD>>Ибо потому что через тупл надо, потому что тут присваивание, а значит {} описывает initializer_list вместо конструктора. Были бы разные типу у it и i2, то даже до этого не дошел бы. BFE>А откуда тут initializer_list ? Зачем он тут?
Потому что так придумали.
A std::initializer_list object is automatically constructed when: a braced-init-list is bound to auto, including in a ranged for loop
BFE>
BFE>struct A
BFE>{
BFE> int n;
BFE> char* s;
BFE>};
BFE>A a = {1, nullptr};
BFE>
BFE>
Это copy-list-initialization и там не initializer_list, а braced-init-list.
BFE>А если убрать присваивание, то почему не работает? BFE>
int i;
char c;
auto list = { i, c }; // initalizer_list, failauto [a, b] = list; // fail даже если list состояится как initializer_list, потому что он не массив и не тупл, значит привязка пойдет по членам класса.
Здравствуйте, andrey.desman, Вы писали:
BFE>>А откуда тут initializer_list ? Зачем он тут? AD>Потому что так придумали.
Предположим, что так, хотя я сомневаюсь.
AD>A std::initializer_list object is automatically constructed when: a braced-init-list is bound to auto, including in a ranged for loop
А это наш случай? У нас structured binding, а не bound to auto. Разве это одно и тоже?
AD>Это copy-list-initialization и там не initializer_list, а braced-init-list.
Что говорит нам о том, что типы не обязаны быть одинаковыми. initializer_list не нужен.
BFE>>А если убрать присваивание, то почему не работает? BFE>>
BFE>>auto [a, b]{i1, i2};
BFE>>
AD>С чего бы ей работать?
С того, что ничему не противоречит. AD>Смотри как объявляется structured binding https://en.cppreference.com/w/cpp/language/structured_binding.
Я смотрел.
AD>В expression вообще запятой быть не положено, а когда ты = убираешь, то оно спотыкается уже на этом.
Не должно быть оператора запятая. Где у меня операторы?
AD>Твой изначальный код — это примерно следующее: AD>auto list = { i, c }; // initalizer_list, fail
initalizer_list отсутствует в structured binding описании.
Предположим, что всё так, как вы пишите. Тогда получается, что это практически безполезная фича языка. Жаль, могла получиться красивая конструкция.
Здравствуйте, B0FEE664, Вы писали:
AD>>A std::initializer_list object is automatically constructed when: a braced-init-list is bound to auto, including in a ranged for loop BFE>А это наш случай? У нас structured binding, а не bound to auto. Разве это одно и тоже?
Наш. См. ниже.
AD>>Это copy-list-initialization и там не initializer_list, а braced-init-list. BFE>Что говорит нам о том, что типы не обязаны быть одинаковыми. initializer_list не нужен.
Тут не обязаны, тут не нужен.
BFE>С того, что ничему не противоречит.
Противоречит описанию.
AD>>В expression вообще запятой быть не положено, а когда ты = убираешь, то оно спотыкается уже на этом. BFE>Не должно быть оператора запятая. Где у меня операторы?
У выражения не может быть не оператора ",", поэтому там именно оператор.
AD>>Твой изначальный код — это примерно следующее: AD>>auto list = { i, c }; // initalizer_list, fail BFE>initalizer_list отсутствует в structured binding описании.
Ему там и не надо быть. Если expression не массив, то.
Otherwise e is defined as if by using its name instead of [ identifier-list ] in the declaration.
Т.е.
auto [a, b] = {1,2,3};
// =>auto e = {1,2,3}; // initializer_list
...
BFE>Предположим, что всё так, как вы пишите. Тогда получается, что это практически безполезная фича языка. Жаль, могла получиться красивая конструкция.
Почему бесполезная? Потому что присваивания параллельными пачками нельзя делать, не написав std::tuple?
Здравствуйте, _NN_, Вы писали:
_NN>Насколько я понимаю в теории можно специализировать std::get для initalizer_list и тогда код соберётся
Там сказано, что можно просто написать:
Здравствуйте, andrey.desman, Вы писали:
AD>Почему бесполезная? Потому что присваивания параллельными пачками нельзя делать, не написав std::tuple?
Почему С++ не называется "с = с + 1"?
Вот и тут так же.
_NN>Или хочется новый синтаксис ввести в язык ?
Так в том-то и дело, что синтаксис новый добавили, но с несколько неожиданным поведением.
_NN>Можно попытаться через stdcpp.ru
Сначало можно тут обсудить.
Например, можно было бы ввести новый оператор, который никакой роли сам в языке по себе не играет, но который можно перегрузить для любого, не только пользовательского типа.
float operator =>(MyVariant from, float to)
{
if ( ! from.IsFloat() )
throw std::bad_cast();
to = from.get<float>();
return to;
}
int operator =>(float from, int to)
{
if ( static_cast<float>(static_cast<int>(from)) != from )
throw std::bad_cast();
to = static_cast<int>(from);
return to;
}
Использование:
MyVariant my(1.2f);
float my => f;
int f => n;
float x = 1.f;
o => x;
std::size_t const size = std::size( data );
for( auto [ i, v ] = enumerate( data ) ) {
if( i == 0 ) {} // первый элемент (или единственный, если size( data ) == 1)else if( i + 1 == size ) {} // последний элементelse {} // между первым и последним
}
Для вывода с запятыми:
for( auto [ i, v ] : enumerate( data ) ) {
if( i != 0 ) {
std::cout << ", ";
}
std::cout << v;
}