C++完美转发:如何避免悬挂引用

C++ perfect forwarding: how to avoid dangling references

考虑以下问题:我有多个 类,每个实现一个 get() 函数。以下 container1container2 是此类 类 的示例:

struct expensive_type {
    int v;
};

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

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

我想创建一个包装器,以 CF 为模板,实现与 C 相同的 get() 功能,但对结果应用一个函数:

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

我现在想为一个简单的包装器创建一个函数 f,只是 returns 它的参数不变。我认为这会起作用:

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() returns 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

问题是该值被转换为右值引用。当您将 转发到 函数时,这通常很好,但您正试图将 转发到 return 值。

您需要 decltype(auto) 类型推导,而不是 auto&& 类型推导。

但实际上您在每个地方都使用了 decltype(auto),但不能使用的地方是:auto&& lambda 参数。

我的解决方案是在 get() return 时按值衰减副本。

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, {} };

现在,即使没有衰减副本,它也会按预期工作。如果您使用衰减副本,则必须写下函数的意图及其行为方式。

这里的问题是没有通过函数临时扩展。当你这样做时

return f(c.get());

c 是一个 container1 你 return 按价值所以你有临时。该临时变量只存在于完整表达式的末尾,这意味着一旦 return 语句完成它就会消失。这就是为什么你有一个悬空引用。

不过,这会让您陷入困境。 f 需要做的是 return 如果它有一个临时的值,但这可能会很昂贵。如果要通过中间函数传递 return 值,确实没有办法解决这个问题。那会给你一个 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;
};

其中 return 右值按值,左值按引用。