C ++ совершенная пересылка: как избежать висячих ссылок - PullRequest
4 голосов
/ 08 ноября 2019

Рассмотрим следующую проблему: у меня есть несколько классов, каждый из которых реализует функцию get(). Следующие container1 и container2 являются примерами таких классов:

struct expensive_type {
    int v;
};

struct container1 {
    expensive_type get() const {
        return { 1 };
    }
};

struct container2 {
    expensive_type x;
    expensive_type& get() {
        return x;
    }
};

Я хочу создать оболочку, настроенную на C и F, которая реализует ту же функциональность get() дляC, но применяет функцию к результату:

template<typename C, typename F>
struct wrapper {
    F f;
    C c;
    decltype(auto) get() {
        return f(c.get());
    }
};

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

auto f = [](auto&& x) -> decltype(auto) {
    return forward<decltype(x)>(x);
};

wrapper<container1, decltype(f)> trivial_wrapper1 { f, {} };
wrapper<container2, decltype(f)> trivial_wrapper2 { f, {} };

, но, к сожалению, trivial_wrapper1.get() возвращает expensive_type{0} вместо expensive_type{1} (по крайней мере, с флагом -O2). Я думаю, что проблема связана с висячими ссылками, но я не могу понять, как это исправить.

Мой вопрос: как правильно реализовать функцию f, чтобы она действовала как идеальная личность, не копируя аргумент?

Для пояснения, вот примеры предполагаемого поведения:

cout << trivial_wrapper1.get().v << endl; // should print 1, prints 0 as of now
trivial_wrapper2.get().v = 2;
cout << trivial_wrapper2.c.x.v << endl; // should print 2, and it does as of now

Ответы [ 2 ]

3 голосов
/ 08 ноября 2019

Эта проблема заключается в том, что нет временного расширения через функции. Когда вы делаете

return f(c.get());

, когда c является container1, вы возвращаетесь по значению, поэтому у вас есть временное значение. Это временное значение доживает только до конца полного выражения, что означает, что оно умирает после завершения оператора return. Вот почему у вас есть свисающая ссылка.

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

auto f = [](auto&& x) -> std::conditional_t<std::is_rvalue_reference_v<decltype(x)>,
                                            std::remove_reference_t<decltype(x)>, 
                                            decltype(x)> {
    return x;
};

, которое возвращает по значению для rvalues ​​и по ссылке для lvalues.

0 голосов
/ 08 ноября 2019

Проблема в том, что значение преобразуется в ссылку на r-значение. Обычно это хорошо, когда вы перенаправляете в функции, но вы пытаетесь перенаправить из возвращаемого значения.

Вместо auto&& вычета типа, вы 'Вам понадобится decltype(auto) удержание.

Но на самом деле вы использовали decltype(auto) как любое место, кроме того, которое вы не можете: auto&& лямбда-параметр.

Моим решением будет распадкопировать при get() возврате по значению.

template<typename C, typename F>
struct wrapper {
    F f;
    C c;
    decltype(auto) get() {
        using get_t = decltype(c.get());
        using f_result_t = decltype(f(c.get()));

        if constexpr (std::is_reference_v<get_t>) {
            return f(c.get());
        } else {
            return std::decay_t<f_result_t>(f(c.get())); // decay copy
        }
    }
};

Решение не очевидно. Что происходит, когда вы делаете это?

auto f = [](auto&& x) -> decltype(auto) {
    static auto v = std::decay_t<decltype(x)>{};
    return (v); // return by ref
};

wrapper<container1, decltype(f)> trivial_wrapper1 { f, {} };
wrapper<container2, decltype(f)> trivial_wrapper2 { f, {} };

Теперь, даже без распадающейся копии, она будет работать так, как ожидалось. Если вы используете распадающуюся копию, вы должны написать цель функции и то, как она должна себя вести.

...