2549 / 1208 / 358
Регистрация: 30.11.2013
Сообщений: 3,826
1

Нужно разобрать смысл строки в variadic Tempalates

15.12.2015, 21:23. Показов 1392. Ответов 9
Метки нет (Все метки)

Author24 — интернет-сервис помощи студентам
Добрый вечер,

хочу понять строки 9-13. С примера вижу, что for_each_argument не вызывает себя рекурсивно, что уже странно для меня - так я думал, что в этом её и смысл судя по моим наработкам в этой области, вот:
Рекурсивный вызов аргументов
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
using namespace std;
 
int sum( int a, int b )
{
   return a + b;
}
int sum( int alone )
{
   return alone;
}
 
template <typename... Nums>
int sum( int a, int b, Nums... nums )
{
   return sum( a, b ) + sum( nums... );
 
   int main()
   {
      std::cout << sum( 3 ) << std::endl;
      std::cout << sum( 3, 1 ) << std::endl;
      std::cout << sum( 3, 1, 2 ) << std::endl;
      std::cout << sum( 3, 1, 2, 15, 15, 123, 46, 7, 12, -1, 345, 99 ) << std::endl;
   }


C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <functional>
#include <iostream>
 
using namespace std;
 
template <typename F, typename... Args>
void for_each_argument( F f, Args&&... args )
{
   using t = int[sizeof...(Args)];
   cout << "start" << endl;
   (void)t
   {
      (f( forward<Args>( args ) ), 0)...
   };
}
 
struct print
{
   template <typename T>
   void operator()( const T& x ) const
   {
      cout << x << endl;
   }
};
 
int main()
{
   for_each_argument( print(), 10, "Hello", 42.5 );
 
}
и теперь судя по "start" я вижу, что она не вызывает себя ресурсивно, а лишь print.operator().

Вопросы:
1) как я понял t это тип int[cout_of_args] - тоесть int[3] для нашего примера
2) зачем там int захардкожен ?
3) как я понял t{} это анонимный массив
4) ... заставит заполнить этот массив 3 раза
5) зачем там 0 и темболе после запятой
6) зачем явное СИ-стайл приведение этого массива к (void)
0
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
15.12.2015, 21:23
Ответы с готовыми решениями:

Variadic Templates - как обращаться к аргументам variadic-функции?
Всем привет! Наверное, рано мне ещё с моими скудными знаниями в это лезть, но, изучив шаблоны и...

разобрать смысл строк
Здравствуйте Уважаемые!!!!!! Очень нужна ваша помощь!!!! Есть исходник программы: Очень нужно...

Variadic templates, или variadic constructor в шаблоне, или прочие извращения
Здравствуйте. Есть такое Wrapper&lt;Obj&gt; w; Wrapper - обертка над объектом того класса, который...

Нужно разобрать код
#include &lt;iostream&gt; #include &lt;vector&gt; #include &lt;algorithm&gt; using namespace std; int main(){ ...

9
Эксперт С++
1674 / 1046 / 174
Регистрация: 27.09.2009
Сообщений: 1,945
15.12.2015, 22:09 2
Лучший ответ Сообщение было отмечено rikimaru2013 как решение

Решение

1) Да.
2) Потому что не используется, используется побочный эффект при заполнении массива нулями.
3) Да.
4) Нет, в массив сразу заливается 3 выражения вида
Код
f(argN), 0
, которые имеют значение 0, но при их вычислении таки вызывается f.
5) Чтобы заполнить массив. Что вернёт f - неизвестно, а если через запятую поставить нолик, то всё выражение и станет ноликом, который смело можно заливать в массив.
6) Чтобы избежать предупреждения о неиспользуемом значении.

В итоге оптимизатор просто выкинет ненужную суету вокруг неиспользуемого массива и заполнения ноликами, оставит только 3 вызова функции f с приведёнными аргументами.

Добавлено через 3 минуты
Вся эта суета нужна только для того, чтобы использовать оператор распаковки на аргументах, вызвав функцию f последовательно для каждого из них. Просто так это сделать синтаксис не даёт, приходится использовать контекст заполнения массива.
1
2549 / 1208 / 358
Регистрация: 30.11.2013
Сообщений: 3,826
15.12.2015, 23:22  [ТС] 3
Nick Alte,
получается:
1) если бы мы не выдумали бы этот анонимный массив, у нас бы не получилось вызвать 3 раза подряд заполнение? Смущает, что играем вокруг массива и значения нуля. Это какое-то особеность?

Цитата Сообщение от Nick Alte Посмотреть сообщение
Что вернёт f - неизвестно, а если через запятую поставить нолик, то всё выражение и станет ноликом
2) получается мы б могли аккамулировать return value
C++
1
2
3
4
5
SomeType k = 0;
(void)t
   {
      ( k += f( forward<Args>( args ) ), 0)...
   };
3) почему строку с using не переписать через typedef, ведь и то и то alias и несут один и тот же смысл?
0
Эксперт С++
8739 / 4317 / 960
Регистрация: 15.11.2014
Сообщений: 9,760
16.12.2015, 00:15 4
Лучший ответ Сообщение было отмечено rikimaru2013 как решение

Решение

Цитата Сообщение от rikimaru2013 Посмотреть сообщение
если бы мы не выдумали бы этот анонимный массив, у нас бы не получилось вызвать 3 раза подряд заполнение?
ага
трюк с массивом используется, что бы избавиться от рекурсии.
Цитата Сообщение от rikimaru2013 Посмотреть сообщение
Смущает, что играем вокруг массива и значения нуля.
массив получается фиктивным.
ну то есть, он не используется.
значения массива нигде не используются.

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

кстати:
C++
1
(void)t
войд в скобочках сообщает компилятору:
мы, программисты, в курсе, что у нас тут неиспользуемая переменная.

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

можно убрать войд и глянуть:
если будет предупреждение,
значит в релизе он эту переменную 100% соптимизирует.

а вот насчет того, сможет ли он соптимизировать рекурсию - вот здесь у меня больше "смущений".




Цитата Сообщение от rikimaru2013 Посмотреть сообщение
получается мы б могли аккамулировать return value
ага

Добавлено через 6 минут
Цитата Сообщение от rikimaru2013 Посмотреть сообщение
зачем там 0 и темболе после запятой
C++
1
2
3
4
(void)t
   {
      (f( forward<Args>( args ) ), 0)...
   };
войд означает: мы в курсе, что t - не используемая переменая
открытые фигурные скобки - инициализация

то есть, мы хотим инициализировать объект t (в данном случае это - массив)
значением:

C++
1
      (f( forward<Args>( args ) ), 0)...
здесь имеет место быть "оператору запятая":
C++
1
( expr1, expr2 )
сначала нужно выполнить expr1, а затем expr2
результатом всего выражения будет expr2

таким образом:

заполнить массив t данными:
C++
1
( запуск-функции, 0)...  и так для каждой переменной из вариадик пакета
то есть каждый раз будет выполняться запуск:
C++
1
f( forward<Args>( args ) )
для каждой переменной из пакета args
но результатом всего выражения будет ноль идущий после запятой

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

потом компилятор пофиксит, что массив не используемый, и оптимизирует его.
останутся только запуски функции

(хотя по хорошему лучше все таки глянуть ассм,
что бы убедиться что он реально оптимизует)
1
2549 / 1208 / 358
Регистрация: 30.11.2013
Сообщений: 3,826
16.12.2015, 00:39  [ТС] 5
Цитата Сообщение от hoggy Посмотреть сообщение
для каждой переменной из пакета args
сложно понять закономерность распоковки

для аргумента метода
C++
1
2
3
4
f(args...) = >  f(args1, args... - args1);      // two parameters
                f(args2, args... - args2);
                f(argsLast);[/ CPP]             // one parameter
                f();                            // zero
для списка инициализации
C++
1
t{ args... } = > t{ args1, args2, argsN };

Стоит ли такие махинации с инициализацией анонимного массива распаковаными аргументами? Как я понял с плюсов, что стек под вызов функции(память под его параметры) выделяются 1 раз, а не рекурсивно (пока не распакован последний существует первый стек вызова). С минисов, студенты и это не осилят
0
Эксперт С++
8739 / 4317 / 960
Регистрация: 15.11.2014
Сообщений: 9,760
16.12.2015, 01:03 6
Цитата Сообщение от rikimaru2013 Посмотреть сообщение
Стоит ли такие махинации с инициализацией анонимного массива распаковаными аргументами? Как я понял с плюсов, что стек под вызов функции(память под его параметры) выделяются 1 раз, а не рекурсивно (пока не распакован последний существует первый стек вызова).
рассмотрим худший вариант:
без оптимизаций, количество разнотипных аргументов велико, тогда:

1. рекурсия.
компилятору придется инстанцировать уйму перегрузок под весь зоопарк типов
что плохо для времени компиляции,
и для размера бинарника.

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

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

2.
фиктивный массив

несмотря на то, что он не используется,
под него придется выделить память.
то бишь зазря израсходуется стековая память в размере sizeof(char)*количество-аргументов-в-пакете

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

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

потому что в случае с рекурсией на обработку одного аргумента приходится копировать весь остаточный вариадик пакет.
а в случае с массивом - только вписать один нолик.

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

в общем, имхо нет смысла заморачиваться.

лично мне нравится способ с фиктивным массивом просто потому,
что тупо меньше кода писать приходится.
0
Игогошка!
1801 / 708 / 44
Регистрация: 19.08.2012
Сообщений: 1,367
16.12.2015, 01:36 7
rikimaru2013, код не идеален на 100%. По-хорошему надо добавить еще одно приведение к void: static_cast<void>(f(forward<Args>(args)).

Цитата Сообщение от hoggy Посмотреть сообщение
но результатом всего выражения будет ноль идущий после запятой
Не всегда
0
Игогошка!
1801 / 708 / 44
Регистрация: 19.08.2012
Сообщений: 1,367
24.12.2015, 12:37 8
rikimaru2013, hoggy, так вы уловили случай, что если я передам функцию f, которая будет возвращать объект типа, у которого перегружен оператор запятая, то оно в лучшем случае не скомпилируется (если оператор будет возвращать не число), а в худшем послужит источником сложно уловимых багов (в случае каких-то side effect'ов)?
Поэтому нужно отбрасывать значение, возвращаемое f, - это делается через ее каст к void.
1
2549 / 1208 / 358
Регистрация: 30.11.2013
Сообщений: 3,826
24.12.2015, 13:12  [ТС] 9
ct0r, уловили же ж. ) А вот вы некрофил - но в хорошом смысле. Хотя какой тут хороший смысл ))
0
Эксперт С++
8739 / 4317 / 960
Регистрация: 15.11.2014
Сообщений: 9,760
24.12.2015, 19:39 10
Цитата Сообщение от ct0r Посмотреть сообщение
Поэтому нужно отбрасывать значение, возвращаемое f, - это делается через ее каст к void.
супер
0
24.12.2015, 19:39
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
24.12.2015, 19:39
Помогаю со студенческими работами здесь

Нужно разобрать тему
Тема старая, и вроде как популярна.Может кто-нибудь с ней уже работал.В общем,у меня есть сайт на...

Нужно разобрать задачу
#include &quot;stdafx.h&quot; #include &quot;chess.h&quot; using namespace std; horse targetHorse;// переменная,...

Нужно разобрать мишь
Как разобрать мышь A4Tech V-q4-500-1 usb? Нигде не могу найти схему как разобрать.

Нужно разобрать код?
С JavaScript (знаю Java)не знаком вообще но худо бедно продвигаюсь и застопорился вот где: var...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
10
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru