Идеальная пересылка вызываемого - PullRequest
0 голосов
/ 29 января 2019

Я придумал следующий код для преобразования R() -подобного в void() -подобный вызываемый объект:

#include <utility>

template<class Callable>
auto discardable(Callable&& callable)
{ return [&]() { (void) std::forward<Callable>(callable)(); }; }
//        ^-- is it ok?

int main()
{
    auto f = discardable([n=42]() mutable { return n--; });
    f();
}

Я беспокоюсь о захвате по ссылке.

  1. Правильно ли оно определено?
  2. Гарантируется ли, что callable никогда не копируется и никогда не используется после окончания срока его жизни?

Это помечено C ++ 14, но применяется ко всем следующим стандартам.

Ответы [ 3 ]

0 голосов
/ 29 января 2019

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

Рабочий пример:

template<class Callable>
auto discardable(Callable&& callable) { // This one makes a copy of the temporary.
    return [callable = std::move(callable)]() mutable {
        static_cast<void>(static_cast<Callable&&>(callable)());
    };
}

template<class Callable>
auto discardable(Callable& callable) {
    return [&callable]() mutable {
        static_cast<void>(callable());
    };
}

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

В качестве примечания, хотя есть новые специализированные утилиты, которые идеально передают категорию значений объекта функции, например std::apply, стандартные библиотечные алгоритмы всегда копируют функциональные объекты, принимая их по значению.Так что если кто-то перегружает operator()()& и operator()()&&, то стандартная библиотека всегда будет использовать operator()()&.

0 голосов
/ 29 января 2019

Лямбды - это анонимные структуры с operator(), список перехвата - причудливый способ указать тип его членов.Захват по ссылке действительно звучит так: у вас есть референтные члены.Нетрудно увидеть ссылки, свисающие.

Это тот случай, когда вы конкретно не хотите совершенную пересылку: у вас разная семантика в зависимости от того, является ли аргумент lvalueили ссылка на значение.

template<class Callable>
auto discardable(Callable& callable)
{
    return [&]() mutable { (void) callable(); };
}

template<class Callable>
auto discardable(Callable&& callable)
{
    return [callable = std::forward<Callable>(callable)]() mutable {  // move, don't copy
        (void) std::move(callable)();  // If you want rvalue semantics
    };
}
0 голосов
/ 29 января 2019

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

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

template<class Callable>
auto discardable(Callable&& callable)
{
    return [f = std::conditional_t<
             std::is_lvalue_reference<Callable>::value,
             std::reference_wrapper<std::remove_reference_t<Callable>>,
             Callable>{std::forward<Callable>(callable)}]
    { 
        std::forward<Callable>(f)(); 
    };
}

Itmove-конструирует временную лямбду.

...