LINUX.ORG.RU

Избежать повторения кода с помощью C++11 range-based for

 ,


5

6

Допустим, есть какие-то единообразные инструкции над несколькими переменными

if(x % 2 == 0) ++x;
if(y % 2 == 0) ++y;
if(z % 2 == 0) ++z;

Как такое делается в современном c++? Я попробовал range-based for, но заработал только вариант с указателями.

for(auto t: {&x, &y, &z})
    if((*t) % 2 == 0) ++(*t);

Reference/dereference тут выглядит явно неуместно. Можно как-то по-другому?

Update

С помощью анонимусов получилось вот такое решение

#include <cstdio>
#include <initializer_list>
#include <functional>

#define rlist(x, ...) (std::initializer_list<std::reference_wrapper<decltype(x)>>({x, __VA_ARGS__}))

int main()
{
    int x = 2, y = 3, z = 4;
    for(auto t : rlist(x, y, z)) if(t % 2 == 0) ++t;
    printf("%d %d %d\n", x, y, z);
    return 0;
}

При использовнии выглядит достаточно прилично (а на макрос можно не смотреть). Требует стандарта C++11. Что с производительностью - пока не знаю.

Update 2

Решение на шаблонах от eao197

#include <iostream>
using namespace std;

template<typename F> void apply_to_all(F &&) {}

template<typename F, typename T, typename... O>
void apply_to_all(F && f, T && x, O && ...other) {
	f(forward<T>(x));
	apply_to_all(forward<F>(f), forward<O>(other)...);
}

int main() {
	int x = 2, y = 3, z = 4, v = 5, w = 6;
        apply_to_all([](int & v) { if(v % 2 == 0) ++v; },
		     x, y, z, v, w);
        apply_to_all([](int v) { cout << v << " "; },
		     x, y, z, v, w);
        cout << endl;
        return 0;
}
На c++14 можно писать auto вместо int в лямбде.

В С++17 за счет if constexpr не нужно будет делать пустую заглушку для прекращения рекурсии вызовов:

template<typename F, typename T, typename... O>
void apply_to_all(F && f, T && x, O && ...other) {
	f(forward<T>(x));
	if constexpr(0 != sizeof...(other))
		apply_to_all(forward<F>(f), forward<O>(other)...);
}

★★★★★

Последнее исправление: Crocodoom (всего исправлений: 3)

Ответ на: комментарий от anonymous

Ну и такой reference_wrapper<T> имеет неявное преобразование к Т&, так что может быть можно даже без .get(), тут не уверен, а проверить пока не могу.

anonymous
()

На основе https://stackoverflow.com/a/26902803

#include <tuple>

template<class F, class...Ts, std::size_t...Is>
void for_each_in_tuple(const std::tuple<Ts...> & tuple, F&& func, std::index_sequence<Is...>){
    using expander = int[];
    (void)expander { 0, ((void)func(std::get<Is>(tuple)), 0)... };
}

template<class F, class...Ts>
void for_each_in_tuple(const std::tuple<Ts...> & tuple, F&& func){
    for_each_in_tuple(tuple, std::forward<F>(func), std::make_index_sequence<sizeof...(Ts)>());
}

int main()
{
    int x, y, z;
    x = y = z = 0;

    for_each_in_tuple(std::tie(x, y, z), [](auto& t) { if (t % 2 == 0) ++t; });
}
utf8nowhere ★★★
()

Если не пугает боост.

#include <tuple>

#include <boost/fusion/algorithm.hpp>
#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/fusion/include/std_tuple.hpp>


int main()
{
    int x, y, z;
    x = y = z = 0;
    
    boost::fusion::for_each(std::tie(x, y, z), [](auto& t) { if (t % 2 == 0) ++t; });
}

utf8nowhere ★★★
()
Ответ на: комментарий от utf8nowhere

Если с С++14, то проще же:

#include <utility>

template <typename ...Args>
auto refs(Args &&...args)
{
    auto result = { std::ref(args)... };
    return result;
}

int main()
{
    int a, b, c;
    for (int &i : refs(a, b, c)) {
        i = 0;
    }
    return a + b + c;
}

Хотя у ТС C++11 написано.

xaizek ★★★★★
()

c++17:

#include <cstdio>

template<class... Args>
void inc( Args&... args ) {
    (... , ++args);
}

int main() {
    int x = 0, y = 0;

    inc();
    inc( x );
    inc( x, y );

    printf( "%d %d\n", x, y );
}
anonymous
()
Ответ на: комментарий от anonymous

for(auto t: std::initializer_list<std::reference_wrapper<int>> {x, y, z} )

И можно еще «шорткат» для std::initializer_list<std::reference_wrapper<T>> сделать через using.

anonymous
()
Ответ на: комментарий от Crocodoom

Завернул в макрос

#include <cstdio>
#include <initializer_list>
#include <functional>

#define rlist(x, ...) (std::initializer_list<std::reference_wrapper<decltype(x)>>({x, __VA_ARGS__}))

int main()
{
    int x = 2, y = 3, z = 4;
    for(auto t : rlist(x, y, z)) if(t % 2 == 0) ++t;
    printf("%d %d %d\n", x, y, z);
    return 0;
}

Вроде работает

Crocodoom ★★★★★
() автор топика
Ответ на: Завернул в макрос от Crocodoom

Достаточно стандарта c++11, кстати.

Наверное можно переписать на variadic templates, но я в них не силён.

Crocodoom ★★★★★
() автор топика
Ответ на: Завернул в макрос от Crocodoom

Можно шаблоном сделать вместо макровыражения

template <typename T>
using rlist = std::initializer_list<std::reference_wrapper<T>>;
и использовать так
for (auto t: rlist<int> { x, y, z }) {
    if (t % 2 == 0) {
        t++;
    }
}

netrino
()
Ответ на: комментарий от netrino

Условие не нужно:

for (auto t: rlist<int> { x, y, z })
  t |= 1;

bormant ★★★★★
()

Пользуйся контейнерами, Люк! Данные у тебя однотипные и обрабатываются одинаково, тогда почему они размазаны по отдельным однобуквенным переменным? Вектор, мапа, не?

anonymous
()

Я фигею с этих C++-нутых. Если С-шник просто напишет макрос или функцию, то плюсатые будут маниакально вставлять более многосимвольную, уродливую конструкцию, обязательно из самого-самого последнего стандарта и ещё срач на эту тему разведут :)

vodz ★★★★★
()

Все предложенные выше варианты читаются НА ПОРЯДОК хуже, чем оригинальное:

if(x % 2 == 0) ++x;
if(y % 2 == 0) ++y;
if(z % 2 == 0) ++z;
Im_not_a_robot ★★★★★
()
Ответ на: комментарий от Im_not_a_robot

++x

Эти штуки сделаны не для людей, а для компиляторов не умеющих оптимизацию. Сегодня надо писать x=x+1

anonymous
()
Ответ на: комментарий от anonymous

Как одиночные выражения они отлично читаются.

p.s покормил тролля — день прошел не зря.

Im_not_a_robot ★★★★★
()
Ответ на: комментарий от xaizek
template<typename auto, auto auto>
auto auto(auto auto) auto
{
    auto auto(auto);
    return auto;
}

Охренеть какой читабельный API получается. Питонолюбы начинают оргазмировать.

dzidzitop ★★
()
Ответ на: комментарий от anonymous

Корень всех зол — нерелевантные цитаты.
А ++ это синтаксический сахар. При этом настолько удобный и привычный, что отказ от него имеет смысла не больше, чем отказ от циклов (на одних if'ах можно делать всё то же самое)

JacobTwoTwo
()

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

#include <iostream>
using namespace std;

void inc_if_even() {}

template<typename... O> void inc_if_even(int & x, O & ...other) {
	if(x % 2 == 0) ++x;
	inc_if_even(forward<O&>(other)...);
}

int main() {
	int x = 2, y = 3, z = 4, v = 5, w = 6;
	inc_if_even(x, y, z, v, w);

	cout << x << " " << y << " " << z << " " << v << " " << w << endl;
	return 0;
}

А если у вас будут разные наборы операций над переменными, то можно сделать обобщенную реализацию apply_to_all:

#include <iostream>
using namespace std;

template<typename F> void apply_to_all(F &&) {}

template<typename F, typename T, typename... O>
void apply_to_all(F && f, T && x, O && ...other) {
	f(forward<T>(x));
	apply_to_all(forward<F>(f), forward<O>(other)...);
}

int main() {
	int x = 2, y = 3, z = 4, v = 5, w = 6;

	apply_to_all(
			[](int & v) { if(v % 2 == 0) ++v; },
			x, y, z, v, w);

	apply_to_all(
			[](int v) { cout << v << " "; },
			x, y, z, v, w);

	cout << endl;

	return 0;
}

В С++17 за счет if constexpr не нужно будет делать пустую заглушку для прекращения рекурсии вызовов:

template<typename F, typename T, typename... O>
void apply_to_all(F && f, T && x, O && ...other) {
	f(forward<T>(x));
	if constexpr(0 != sizeof...(other))
		apply_to_all(forward<F>(f), forward<O>(other)...);
}

eao197 ★★★★★
()
Ответ на: комментарий от vodz

Да-да. Немногословное говно типа printf напишет за месяц (с отладкой) и потом будет собирать уязвимости по полям из-за неправильного использования в рантайме.

dzidzitop ★★
()
Ответ на: комментарий от anonymous

Тебе в жаваскрипт надо, последователь культа свидетелей Оптимизируещего Компилятора.

dzidzitop ★★
()
Ответ на: комментарий от anonymous

Ты бы про переопределение операторов почитал, дружок, а потом уже корнями зла размахивал.

dzidzitop ★★
()

Уместно тут смотрятся единнобразные инструкции или хранение единообразнообрабатываемых переменных в массиве. А всё остальное нафиг не надо. Лишние проблемы при чтении кода.

dzidzitop ★★
()
Ответ на: комментарий от vodz

Я фигею с этих C++-нутых. Если С-шник просто напишет макрос или функцию, то плюсатые будут маниакально вставлять более многосимвольную, уродливую конструкцию, обязательно из самого-самого последнего стандарта и ещё срач на эту тему разведут :)

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

- простота в использовании;
- гарантия вычисления на этапе компиляции, если это потребуется;
- отсутствие побочных эффектов при вызове;
- автоматический вывод типа результата, а не тупо int, например.

Код на С++:

#include <iostream>
using namespace std;

template <typename T, typename U>
constexpr typename std::common_type<T, U>::type min(T&& a, U&& b) {
    return a < b ? std::forward<T>(a) : std::forward<U>(b);
}

template <typename T, typename... Args>
constexpr typename std::common_type<T, Args...>::type min(T&& a, Args&&... args) {
    return min(std::forward<T>(a), min(std::forward<Args>(args)...));
}

int main() {
    cout << min(10, 1.333, rand(), rand() / 2.) << endl;
    cout << min(10, 9, 8, 7) << endl;
}

А как ты это сделаешь? Подойдут и расширения GCC. Если что в стандарте С++ есть уже реализация, в отличие от С, где авторы языка понимают насколько убоги макросы и что делать через них не вариант.

anonymous
()
Ответ на: комментарий от anonymous

А как ты это сделаешь?

Пф, я где-то говорил о том, что надо бросить плюсы и писать на C? Значить в точку попал, что сбросились на абразуру защищать всё что попало в своём зоопарке, даже не разбираясь о чём речь.

vodz ★★★★★
()
Ответ на: комментарий от vodz

Пф, я где-то говорил о том, что надо бросить плюсы и писать на C? Значить в точку попал, что сбросились на абразуру защищать всё что попало в своём зоопарке, даже не разбираясь о чём речь

Еще раз, подобный топик на форуме - это повод пофлудить на тему, что и как можно сделать в принципе. Это ты влез с бесполезным оценочным вбросом, а теперь еще и свои комплексы на других натягиваешь. Лично меня С устраивает, но я понимаю его слабые места, и мне смешно слышать, что на сишных макросах, например, можно строить сложную логику. На них даже MIN/MAX для двух аргументов полноценный не сделать, без расширений компилятора.

anonymous
()
Ответ на: комментарий от anonymous

Еще раз,

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

vodz ★★★★★
()
Ответ на: комментарий от vodz

Ещё раз, в вас говорит обида по поводу которого не было.

Мимо, я уже сказал, что мне не принципиально, в отличие, вероятно, от тебя.

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

Если ты читал исходную задачу, то мог случайно заметить слово «допустим». Т.е. это не реальная задача, которую решает ТС, это минимальный пример, чтоб была понятна суть. И именно потому все подобные придирки нелепы и глупы. Для примера возьмем ядро Linux:

#define min(x, y) ({				\
	typeof(x) _min1 = (x);			\
	typeof(y) _min2 = (y);			\
	(void) (&_min1 == &_min2);		\
	_min1 < _min2 ? _min1 : _min2; })

#define min3(x, y, z) ({			\
	typeof(x) _min1 = (x);			\
	typeof(y) _min2 = (y);			\
	typeof(z) _min3 = (z);			\
	(void) (&_min1 == &_min2);		\
	(void) (&_min1 == &_min3);		\
	_min1 < _min2 ? (_min1 < _min3 ? _min1 : _min3) : \
		(_min2 < _min3 ? _min2 : _min3); })

#define min_t(type, x, y) ({			\
	type __min1 = (x);			\
	type __min2 = (y);			\
	__min1 < __min2 ? __min1: __min2; })

Это «выглядит запутаннее, больше и требует поддержки» GCC-ов. А можно ведь взять простой тернарный оператор и не париться. Наверное, Линус не умеет писать простой код.

anonymous
()
Ответ на: комментарий от eao197

Спасибо. Это можно переписать (не используя макросы), чтобы можно было пользоваться как в [1] или [2]? Тогда добавил бы в стартовый пост. Просто решение с макросами там уже есть, теперь хочется на чистых шаблонах. Соблюдать C++11 не обязательно.

[1]

apply_to_all(if(x % 2 == 0) ++x,
             x, y, z, v, w);                            
apply_to_all(cout << x << " ",
             x, y, z, v, w);

[2]

apply_to_all<x, y, z, v, w>(if(x % 2 == 0) ++x);   
apply_to_all<x, y, z, v, w>(cout << x << " ");

С макросом [1] у меня выглядит примерно так

template<typename F> void _apply_to_all(F &&) {}                                
                                                                                
template<typename F, typename T, typename... O>                                 
void _apply_to_all(F && f, T && x, O && ...other)                               
{                                                                               
    f(forward<T>(x));                                                           
    _apply_to_all(forward<F>(f), forward<O>(other)...);                         
}                                                                               
                                                                                
#define apply_to_all(act, obj, ...) (_apply_to_all([](decltype(obj) & obj) {act;}, obj, __VA_ARGS__))

P.S. Лямбды из Вашего кода точно развернуться во время компиляции?

Crocodoom ★★★★★
() автор топика
Ответ на: комментарий от anonymous

Наверное, Линус не умеет писать простой код.

Вы то — точно. Слабо не съезжать с темы в вашу боль насчёт min(), а показать, что предлагаемая замена в КАЖДОМ месте, где используется, а не в виде макроса, пусть и уродского, но одного - выглядит лучше. Вот ТС таки понял и апдейтнул с макросом. А если сравнить с вывертами, что тут предлагали с c++14?

vodz ★★★★★
()
Последнее исправление: vodz (всего исправлений: 1)
Ответ на: комментарий от vodz

Вы то — точно. Слабо не съезжать с темы в вашу боль насчёт min()

Опять вместо аргументов переход на личности. Еще раз такое повторится и уйдешь в игнор.

показать, что предлагаемая замена в КАЖОМ месте, где используется, а не в виде макроса, пусть и уродского, но одного - выглядит лучше.

А кто говорит про каждое место? Что за детский максимализм? А ТС как раз с макросами начал чудить.

А если сравнить с вывертами, что тут предлагали с c++14?

Тебе вообще знакомо понятие «мозговой штурм»? Ну вот представь, что топик на ЛОР это он.

anonymous
()
Ответ на: комментарий от anonymous

Опять вместо аргументов переход на личности.

Во-первых, этот мем так тут заездили, что уже никого не волнует, причём особенно, когда этого перехода и не было. Да даже если и было. Вам очень вежливо 3 раза намекали, что ваш C-шный пример совсем не в кассу.

А кто говорит про каждое место?

Топик же, выше до того комментария, с которого вы и начали срач.

Еще раз такое повторится и уйдешь в игнор.

Сказал аноним. Спасибо, я посмеялся.

vodz ★★★★★
()
Ответ на: комментарий от Crocodoom

Это можно переписать (не используя макросы), чтобы можно было пользоваться как в [1] или [2]?

Вряд ли. В C++ пока нет возможности лаконично записывать лямбды.

Лямбды из Вашего кода точно развернуться во время компиляции?

Должны. Можете проверить на https://godbolt.org/ Только нужно -02 в опциях не забыть поставить.

Я не удивлюсь, если там в compile-time еще и все значения подсчитаются и никаких вызовов в run-time не будет: https://godbolt.org/g/sdD2gc

eao197 ★★★★★
()

В С++17 за счет if constexpr не нужно будет делать пустую заглушку для прекращения рекурсии вызовов:

Зачем в C++17 вообще какая-то рекурсия, когда есть fold expressions? (Аноним их уже показывал в Избежать повторения кода с помощью C++11 range-based for (комментарий))

Просто и понятно:

template<typename F, typename... Args>
void apply_to_all(F&& f, Args&&... args)
{
	(f(std::forward<Args>(args)), ...);
}
https://wandbox.org/permlink/KNaGfFNI7SudBSJr

(опасающиеся перегрузки оператора запятая могут приправить это кастом к void).

utf8nowhere ★★★
()
Ответ на: комментарий от Im_not_a_robot

Все предложенные выше варианты читаются НА ПОРЯДОК хуже, чем оригинальное:

Согласен, тред отлично иллюстрирует главный недостаток современного С++ — тривиальную задачу можно решить десятком разных способов, каждый программист будет писать на том подмножестве которое он знает, в итоге поддержка проекта неоправданно усложняется на ровном месте.

CrossFire ★★★★★
()

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

//[[[cog
//for v in ['x', 'y', 'z']:
//      cog.outl("if({val} % 2 == 0) ++{val};".format(val=v))
//]]]
if(x % 2 == 0) ++x;
if(y % 2 == 0) ++y;
if(z % 2 == 0) ++z;
//[[[end]]]
То, что в комментариях - код на питоне, который генерирует то, что между [[[cog...]]] и [[[end]]] по команде
cog.py -r test.cpp
Просто, понятно, легко модицифицируется.

tim239 ★★
()
Ответ на: комментарий от Crocodoom

Это можно переписать (не используя макросы)

Проклятые плюсофилы совсем слетели с катушек. То, что ты хочешь, элементарно и эффективно делается на макросах. И не нужно никаких -O2 и танцов, новомодных стандартов и упования на отсутствие регрессий в компиляторах.

#define VARLIST(AA) AA(x) AA(y) AA(z)
#define DOFOO(var) if (var % 2 == 0) ++var;
VARLIST(DOFOO);
#define DOBAR(var) if (var % 3 == 0) --var;
VARLIST(DOBAR);
kawaii_neko ★★★★
()
Ответ на: комментарий от kawaii_neko

Проклятые плюсофилы совсем слетели с катушек. То, что ты хочешь, элементарно и эффективно делается на макросах.

Проклятые сишники совсем слетели с катушек. Для таких простых случаев в С надо оставлять копипасту, а для сложных код просто выносится в функцию. А «#define VARLIST(AA) AA(x) AA(y) AA(z)» это детский сад.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.