C++完美转发:如何避免悬挂引用
C++ perfect forwarding: how to avoid dangling references
考虑以下问题:我有多个 类,每个实现一个 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
为模板,实现与 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 右值按值,左值按引用。
考虑以下问题:我有多个 类,每个实现一个 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
为模板,实现与 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 右值按值,左值按引用。