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

问题描述 投票:3回答:2

考虑以下问题:我有许多类,每个类都实现一个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为模板的包装器,该包装器实现与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
c++ perfect-forwarding
2个回答
0
投票

问题是该值被转换为r值引用。当转发into函数时,这通常很好,但是您尝试转发out返回值。

代替auto&&类型演绎,您需要decltype(auto)类型演绎。

但是实际上,您将decltype(auto)用作除以下以外的所有位置:auto&& lambda参数。

我的解决方案是在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, {} };

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


0
投票

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

return f(c.get());

ccontainer1时,您按值返回,因此具有临时权限。该临时变量只存在于完整表达式的末尾,这意味着一旦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;
};

[对于右值,按值返回,对于左值,按引用返回。

© www.soinside.com 2019 - 2024. All rights reserved.