Правильное использование for_each_arg — слишком много переадресации?

Я очень рад, что обнаружил for_each_arg(...), который значительно упрощает работу с пакетами аргументов.

template<class F, class...Ts>
F for_each_arg(F f, Ts&&...a) {
 return (void)std::initializer_list<int>{(ref(f)((Ts&&)a),0)...}, f;
}

Я, однако, смущен его правильным использованием. Есть много аргументов, которые должны быть идеально перенаправлены, но выполняю ли я какую-либо ненужную пересылку?

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

struct UselessContainer
{
    // Expects a perfectly-forwarded item to emplace
    template<typename T> void add(T&&) { }   
};

// Creates an `UselessContainer` already filled with `mArgs...`
auto makeUselessContainer(TArgs&&... mArgs)
{
    using namespace std;
    UselessContainer result;

    for_each_arg
    (
        [&result, &mArgs...] // Am I capturing the `mArgs...` pack correctly here?
        (auto&& mX) // Am I passing the arguments to the lambda correctly here?
        { 
            // Is this `forward` necessary?
            result.add(forward<decltype(mX)>(mX)); 

            // Could it be replaced with
            // `result.add(forward(mX));` 
            // ?             
        }, 
        forward<TArgs>(mArgs)... // I assume this `forward` is necessary.
    );

    return result;
}

Все мои вопросы/сомнения высказаны в комментариях к приведенному выше примеру кода.


person Vittorio Romeo    schedule 01.02.2015    source источник
comment
Я не понимаю, зачем вам нужно захватывать стаю. Остальное выглядит нормально. Вы перенаправляете аргументы в for_each_arg, который перенаправляет их в лямбду, которая должна перенаправить их в add().   -  person T.C.    schedule 01.02.2015


Ответы (2)


Каждый forward в вашем коде действительно необходим для идеальной пересылки всех аргументов до конца. Имена ссылок rvalue являются значениями lvalue, поэтому, если вы не пересылаете каждый раз, когда передаете аргументы, информация о категории значений будет потеряна.
Также невозможно вызвать forward без явного списка аргументов шаблона, поскольку параметр шаблона используется только в один, невыведенный контекст. На самом деле шаблон функции, вызываемый без явного списка аргументов, не может выполнить эту работу.

Вы можете попробовать макрос, чтобы несколько сократить код:

#define FORWARD(...) std::forward<decltype(__VA_ARGS__)>(__VA_ARGS__)

Затем он становится

for_each_arg
(
    // Removed superfluous capture
    [&result] (auto&& mX) { 
        result.add(FORWARD(mX));       
    }, 
    FORWARD(mArgs)...
);

Также можно использовать макрос вместо for_each_arg в первую очередь:

#define FOR_EACH_ARG(...) (void)std::initializer_list<int>{((__VA_ARGS__),0)...}

FOR_EACH_ARG( result.add(forward<TArgs>(mArgs)) );
person Columbo    schedule 01.02.2015
comment
Слава богу, в C++17 нам нужно сделать только ((void)result.add(forward<TArgs>(mArgs)) , ...); - person T.C.; 01.02.2015
comment
@TC: Является ли это частью предложения по выражениям сгиба? Как я могу узнать больше об этом? - person Vittorio Romeo; 01.02.2015
comment
@Deduplicator Не требуется, выражение fold работает и с пустыми пакетами (для некоторых операторов, включая ,). - person T.C.; 01.02.2015
comment
@VittorioRomeo Исходное предложение: N4191 ; окончательная формулировка: N4295. - person T.C.; 01.02.2015
comment
@Т.С. Почему вы транслируете на void? - person Columbo; 01.02.2015
comment
@Columbo В этом случае нет необходимости; защита от перегруженной запятой для общего случая. - person T.C.; 01.02.2015
comment
@columbo Что, если add вернет struct evil {void operator,(evil){delete this;}};? ;) - person Yakk - Adam Nevraumont; 01.02.2015
comment
@Yakk ... но это не так - person Columbo; 01.02.2015
comment
@Т.С. Хорошая точка зрения. Для обычных расширений пакета неявная запятая никогда не вызывает оператор запятой, хотя я все путал. - person Columbo; 01.02.2015
comment
@columbo может быть специализация, введенная автором evil. Бвахахаха. ахум. (или какой-то другой тип) - person Yakk - Adam Nevraumont; 01.02.2015
comment
@Columbo: Не имеет отношения, но мне очень нравится использование прямого макроса. Делает код более читабельным. Это применимо к каждой ситуации? - Подумайте о наличии пакета параметров TArgs&&... mArgs. Будет ли запись FORWARD(mArgs)... эквивалентна написанию std::forward<TArgs>(mArgs)...? - person Vittorio Romeo; 01.02.2015

for_each_arg (
  [&](auto&& mX){
    result.add(std::forward<decltype(mX)>(mX));
  },
  std::forward<TArgs>(mArgs)...
);

Просто захватите & при создании такой лямбды. Если вы должны перечислить, нужно захватить только &result.

forward<?> всегда используется с параметром типа.

Обратите внимание, что for_each_arg Эрика несовершенен и в основном состоит из 140 символов или меньше. ;) Его несовершенства здесь мягкие и безобидные.

Вот альтернатива:

Сначала напишите это:

template<class...Fs>
void do_in_order(Fs&&...fs){
  int _[]={0,
    (((void)(std::forward<Fs>(fs)())),0)...
  };
  (void)_; // kills warnings
}

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

Затем замените вызов for_each_arg на:

do_in_order(
  [&]{
    result.add(std::forward<TArgs>(mArgs));
  }...
);

недостатком является то, что большему количеству компиляторов это не понравится.

Порядок выражений в do_in_order гарантируется разделами [dcl.init] и [dcl.init.list] в n4296 8.5.4/4 8.5.4/1 8.5/15 8.5/1. Инициализация — это инициализация-списка-копирования (8.5/15 и 8.5.4/1), это «список-инициализаторов из списка-инициализации в фигурных скобках» ( 8.5/1) и, таким образом, упорядочены слева направо (8.5.4/4).

person Yakk - Adam Nevraumont    schedule 01.02.2015
comment
Не знал о возможных недостатках for_each_arg. На самом деле меня не волнует продолжительность его реализации, поэтому, если можно исправить эти недостатки, мне бы очень хотелось знать, как это сделать! for_each_arg стала одной из моих любимых функций :) - person Vittorio Romeo; 01.02.2015
comment
@vitt должен взять F на F&&, чтобы избавиться от возможно бесполезной копии. Возврат F сомнительный, и если это произойдет, его следует перенаправить в (хорошо перемещенный в существующий код). std::ref(f) бессмысленно - просто f. В целом, это часто требует дополнительной пересылки (сравните два вышеприведенных) и шаблонного кода. - person Yakk - Adam Nevraumont; 01.02.2015
comment
Ну, std::ref не совсем бессмысленно, только в основном. Что, если вы хотите вызвать один и тот же указатель на функцию-член для всего списка объектов? Помните, что ref(f)(...) имеет семантику INVOKE(f, ...). - person Deduplicator; 01.02.2015
comment
@deduplicator хех, правда: но вызывающий абонент может std::ref, если его там не было. Я думаю, это безвредно - поддерживает ли ref rvalue ()? - person Yakk - Adam Nevraumont; 01.02.2015
comment
Не для его аргумента. Но возвращенный reference_wrapper делает все остальное. - person Deduplicator; 01.02.2015
comment
@Yakk В случае указателя на функцию-член вызывающая сторона может std::mem_fn его. - person T.C.; 01.02.2015
comment
Я почти уверен, что инициализаторы в int _[] = { stuff... }; не обязательно будут оцениваться слева направо. Новое поведение всегда слева направо (по крайней мере, в С++ 11) применяется только к универсальному синтаксису инициализации, в котором необходимо опустить знак =. - person Quuxplusone; 02.02.2015
comment
@Quuxplusone Я не вижу, чтобы этот аргумент поддерживался стандартом. Я отредактировал свой пост, чтобы он содержал ссылки на различные разделы, которые, по моему мнению, применимы. - person Yakk - Adam Nevraumont; 02.02.2015