Захват копии пакета параметров - PullRequest
0 голосов
/ 28 октября 2018

Мой класс имеет член типа optional<A>.Я пытаюсь реализовать функцию emplaceWhenReady, которая получает список параметров для конструктора А, но нетривиальная часть заключается в том, что A может быть инициализирован только после определенного события.Когда emplaceWhenReady вызывается перед событием, мне нужно каким-то образом захватить значения инициализации.

Для одного параметра конструктора код можно записать в виде:

struct B {
   bool _ready;
   std::optional<A> _a;
   std::function<void()> _fInit;

   template<typename ARG>
   void emplaceWhenReady1(ARG&& arg) {
      if (_ready) {
         _a.emplace(std::forward<ARG>(arg));
      } else {
         _fInit = [this, argCopy = std::forward<ARG>(arg)]() {
            _a.emplace(std::move(argCopy));
      };
    }
};

и _fInit() canтеперь вызываться, когда класс становится _ready.Но я не могу написать похожий код для нескольких параметров:

// Fails to compile
template<typename... ARGS>
void emplaceWhenReady(ARGS&&... args) {
    if (_ready) {
        _a.emplace(std::forward<ARGS>(args)...);
    } else {
        _fInit = [this, argsCopy = std::forward<ARGS>(args)...]() {
            _a.emplace(std::move(argsCopy)...);
        };
    }
}

Godbolt: https://godbolt.org/z/Fi3o1S

error: expected ',' or ']' in lambda capture list
       _fInit = [this, argsCopy = std::forward<ARGS>(args)...]() {
                                                          ^

Любая помощь приветствуется!

Ответы [ 2 ]

0 голосов
/ 28 октября 2018

С некоторой помощью (см. Ответ и комментарии выше) я смог найти решение:

    template<typename... ARGS>
    void emplaceWhenReady(ARGS&&... args) {
      if (_ready) {
        _a.emplace(std::forward<ARGS>(args)...);
      } else {
        _fInit = [this, argsCopy = std::make_tuple(std::forward<ARGS>(args)...)]() {
            auto doEmplace = [&](auto&... params) {
                _a.emplace(std::move(params)...);
            };
            std::apply(doEmplace, argsCopy);
        };
      }
    }
0 голосов
/ 28 октября 2018

Если мы посмотрим на предложение p0780: Разрешить расширение пакета в lambda init-capture , оно охватывает эту проблему и возможные решения:

С введением обобщенного лямбда-захвата [1] лямбда-захваты могут быть почти произвольно сложными и решать практически все проблемы.Тем не менее, все еще остается неловкая дыра в возможностях лямбда-захвата, когда речь идет о пакетах параметров: вы можете захватывать пакеты только копией, ссылкой или ... std :: tuple?

Простой пример попытки обернуть функцию и ее аргументы в вызываемый объект, к которому можно будет получить доступ позже.Если мы скопируем все, то реализация будет легко написать и прочитать:

template<class F, class... Args>
auto delay_invoke(F f, Args... args) {
    // the capture here can also be just [=]
    return [f, args...]() -> decltype(auto) {
        return std::invoke(f, args...);
    };
}

Но если мы попытаемся быть более эффективными в реализации и попытаться переместить все аргументы в лямбду?Похоже, вы должны быть в состоянии использовать init-capture и писать:

template<class F, class... Args>
auto delay_invoke(F f, Args... args) {
    return [f=std::move(f), ...args=std::move(args)]() -> decltype(auto) {
        return std::invoke(f, args...);
    };
}

Но это противоречит очень явной формулировке из [expr.prim.lambda.capture] / 17, выделение мое:

Простой захват с последующим многоточием - расширение пакета.Захват init, за которым следует многоточие, имеет неправильную форму.

В нем рассматриваются различные решения, в том числе с использованием кортежа:

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

template<class F, class... Args>
auto delay_invoke(F f, Args... args) {
    return [f=std::move(f), tup=std::make_tuple(std::move(args)...)]() -> decltype(auto) {
        return std::apply(f, tup);
    };
}

Что еще хуже, если мы хотели сделать с этим захваченным пакетом параметров вызов функции с именем, а не захваченного объекта.В этот момент все подобие понимания выходит за рамки:

Это предложение было объединено в проекте стандарта в марте , поэтому мы должны получить это изменение в C ++ 2a.

...