std::forward<std::decay_t<F>>(f) 的含义
Meaning of std::forward<std::decay_t<F>>(f)
我对https://koturn.hatenablog.com/entry/2018/06/10/060000中编写的代码有疑问
当我传递一个左值引用时,如果我不使用 std::decay_t 删除引用,我会得到一个错误。
这是错误信息
'错误:'operator()' 不是'main()::&
的成员
不明白为什么要排除左值引用
我想知道这个错误是什么意思。
#include <iostream>
#include <utility>
template <typename F>
class
FixPoint : private F
{
public:
explicit constexpr FixPoint(F&& f) noexcept
: F{std::forward<F>(f)}
{}
template <typename... Args>
constexpr decltype(auto)
operator()(Args&&... args) const
{
return F::operator()(*this, std::forward<Args>(args)...);
}
}; // class FixPoint
namespace
{
template <typename F>
inline constexpr decltype(auto)
makeFixPoint(F&& f) noexcept
{
return FixPoint<std::decay_t<F>>{std::forward<std::decay_t<F>>(f)};
}
} // namespace
int
main()
{
auto body = [](auto f, int n) -> int {
return n < 2 ? n : (f(n - 1) + f(n - 2));
};
auto result = makeFixPoint(body)(10);
std::cout << result << std::endl;
}
模板无法按预期方式工作。
首先,因为应该可以继承自F
,所以使用std::decay_t
意义不大。相反,它可能应该只是 std::remove_cvref_t
,但在 C++20 之前写起来有点麻烦,而 std::decay_t
几乎是一样的。
在 class 模板中 F
旨在成为 non-reference 类型。它不是构造函数的模板参数。所以它不能用于perfect-forwarding。构造函数参数将始终是不能采用左值的右值引用。
即使将左值传递给 makeFixPoint
,调用 std::forward<std::decay_t<F>>(f)
也会强制转换为右值引用,然后可以在构造函数中使用。这显然是危险的。函数 always 的行为就像您将参数 std::move
d 放入其中一样。
它应该只是std::forward<F>(f)
并且构造函数应该采用另一个可用于形成转发引用的模板参数:
template<typename G>
requires std::is_same_v<std::remove_cvref_t<G>, F>
explicit constexpr FixPoint(G&& g) noexcept
: F{std::forward<G>(g)}
{}
I don't understand why it is necessary to exclude the left value
reference. I would like to know what this error means.
当您将 lvalue lambda 传递给 makeFixPoint()
时,makeFixPoint()
的模板参数 F
被实例化为 L&
,其中 L
是 lambda 类型。在函数体中,FixPoint<std::decay_t<F>>{...}
会被实例化为FixPoint<L>{...}
,所以FixPoint
的构造函数被实例化为
explicit constexpr FixPoint(L&& f);
它接受 lambda 类型的 rvalue 引用。在 makeFixPoint()
中,如果您使用 {std::forward<F>(f)}
初始化它,即 {std::forward<L&>(f)}
,f
将作为 lvalue 转发,这将是 ill-formed 因为 rvalue 引用不能绑定 lvalue.
使用{std::forward<std::decay_t<F>>(f)}
的目的是强制f
作为右值转发。
,你分享的代码有毒
template <typename F>
class FixPoint : private F
{
public:
explicit constexpr FixPoint(F&& f)
这意味着我们期望 F
是一个值类型(因为从引用继承是不可能的)。此外,我们将只接受右值——我们将 仅 从另一个 F
.
移动
: F{std::forward<F>(f)}
{}
这个std::forward<F>
毫无意义;这表明编写此代码的人 认为 他们是完美转发:他们不是。唯一合法的类型 F
是值类型(不是引用),如果 F
是值类型 F&&
总是右值引用,因此 std::forward<F>
总是等价于std::move
.
有些情况下你想要
template<class X>
struct wrap1 {
X&& x;
wrap1(X&& xin):x(std::forward<X>(xin)){}
};
甚至
template<class X>
struct wrap2 {
X x;
wrap2(X&& xin):x(std::forward<X>(xin)){}
};
所以上面的代码与某些用例相似,但它不是那些用例之一。 (这里的区别是X
或X&&
是成员的类型,不是基class;基class不能是引用)。
wrap2
的用途是当你想“延长生命周期”右值,并简单地引用左值时。 wrap1
的用途是当你想继续完美转发某些表达式时(wrap1
样式对象通常不安全地保留超过一行代码;wrap2
是安全的只要它们的寿命不超过传递给它们的任何左值。
template <typename F>
inline constexpr decltype(auto)
makeFixPoint(F&& f) noexcept
{
return FixPoint<std::decay_t<F>>{std::forward<std::decay_t<F>>(f)};
}
好的,这里有更多危险信号。 inline constexpr
是废话的标志; constexpr
函数总是 inline
。可能有些编译器将额外的 inline
视为某种意义,因此不能保证一定会出现问题。
return FixPoint<std::decay_t<F>>{std::forward<std::decay_t<F>>(f)};
std::forward
是有条件的着法。如果有右值或值类型传递给它,它就会移动。 decay
保证 return 一个值类型。所以我们只是去掉了操作的条件部分,把它变成了一个原始的移动。
return FixPoint<std::decay_t<F>>{std::move(f)};
这个比较简单,意思一样。
此时你可能会看到危险:
template <typename F>
constexpr decltype(auto)
makeFixPoint(F&& f) noexcept
{
return FixPoint<std::decay_t<F>>{std::move(f)};
}
我们无条件地从 makeFixPoint
论点转移。名称 makeFixPoint
没有任何内容表示“我从参数中移出”,并且它接受转发引用;所以它会默默地消耗一个左值和move-from它。
这很粗鲁。
所以代码的合理版本是:
template <typename F>
class FixPoint : private F
{
public:
template<class U,
class dU = std::decay_t<U>,
std::enable_if_t<!std::is_same_v<dU, FixPoint> && std::is_same_v<dU, F>, bool> = true
>
explicit constexpr FixPoint(U&& u) noexcept
: F{std::forward<U>(u)}
{}
[截图]
namespace
{
template <typename F>
constexpr FixPoint<std::decay_t<F>>
makeFixPoint(F&& f) noexcept
{
return FixPoint<std::decay_t<F>>{std::forward<F>(f)};
}
} // namespace
稍微清理一下。
我对https://koturn.hatenablog.com/entry/2018/06/10/060000中编写的代码有疑问
当我传递一个左值引用时,如果我不使用 std::decay_t 删除引用,我会得到一个错误。
这是错误信息
'错误:'operator()' 不是'main()::
的成员
不明白为什么要排除左值引用
我想知道这个错误是什么意思。
#include <iostream>
#include <utility>
template <typename F>
class
FixPoint : private F
{
public:
explicit constexpr FixPoint(F&& f) noexcept
: F{std::forward<F>(f)}
{}
template <typename... Args>
constexpr decltype(auto)
operator()(Args&&... args) const
{
return F::operator()(*this, std::forward<Args>(args)...);
}
}; // class FixPoint
namespace
{
template <typename F>
inline constexpr decltype(auto)
makeFixPoint(F&& f) noexcept
{
return FixPoint<std::decay_t<F>>{std::forward<std::decay_t<F>>(f)};
}
} // namespace
int
main()
{
auto body = [](auto f, int n) -> int {
return n < 2 ? n : (f(n - 1) + f(n - 2));
};
auto result = makeFixPoint(body)(10);
std::cout << result << std::endl;
}
模板无法按预期方式工作。
首先,因为应该可以继承自F
,所以使用std::decay_t
意义不大。相反,它可能应该只是 std::remove_cvref_t
,但在 C++20 之前写起来有点麻烦,而 std::decay_t
几乎是一样的。
在 class 模板中 F
旨在成为 non-reference 类型。它不是构造函数的模板参数。所以它不能用于perfect-forwarding。构造函数参数将始终是不能采用左值的右值引用。
即使将左值传递给 makeFixPoint
,调用 std::forward<std::decay_t<F>>(f)
也会强制转换为右值引用,然后可以在构造函数中使用。这显然是危险的。函数 always 的行为就像您将参数 std::move
d 放入其中一样。
它应该只是std::forward<F>(f)
并且构造函数应该采用另一个可用于形成转发引用的模板参数:
template<typename G>
requires std::is_same_v<std::remove_cvref_t<G>, F>
explicit constexpr FixPoint(G&& g) noexcept
: F{std::forward<G>(g)}
{}
I don't understand why it is necessary to exclude the left value reference. I would like to know what this error means.
当您将 lvalue lambda 传递给 makeFixPoint()
时,makeFixPoint()
的模板参数 F
被实例化为 L&
,其中 L
是 lambda 类型。在函数体中,FixPoint<std::decay_t<F>>{...}
会被实例化为FixPoint<L>{...}
,所以FixPoint
的构造函数被实例化为
explicit constexpr FixPoint(L&& f);
它接受 lambda 类型的 rvalue 引用。在 makeFixPoint()
中,如果您使用 {std::forward<F>(f)}
初始化它,即 {std::forward<L&>(f)}
,f
将作为 lvalue 转发,这将是 ill-formed 因为 rvalue 引用不能绑定 lvalue.
使用{std::forward<std::decay_t<F>>(f)}
的目的是强制f
作为右值转发。
,你分享的代码有毒
template <typename F>
class FixPoint : private F
{
public:
explicit constexpr FixPoint(F&& f)
这意味着我们期望 F
是一个值类型(因为从引用继承是不可能的)。此外,我们将只接受右值——我们将 仅 从另一个 F
.
: F{std::forward<F>(f)}
{}
这个std::forward<F>
毫无意义;这表明编写此代码的人 认为 他们是完美转发:他们不是。唯一合法的类型 F
是值类型(不是引用),如果 F
是值类型 F&&
总是右值引用,因此 std::forward<F>
总是等价于std::move
.
有些情况下你想要
template<class X>
struct wrap1 {
X&& x;
wrap1(X&& xin):x(std::forward<X>(xin)){}
};
甚至
template<class X>
struct wrap2 {
X x;
wrap2(X&& xin):x(std::forward<X>(xin)){}
};
所以上面的代码与某些用例相似,但它不是那些用例之一。 (这里的区别是X
或X&&
是成员的类型,不是基class;基class不能是引用)。
wrap2
的用途是当你想“延长生命周期”右值,并简单地引用左值时。 wrap1
的用途是当你想继续完美转发某些表达式时(wrap1
样式对象通常不安全地保留超过一行代码;wrap2
是安全的只要它们的寿命不超过传递给它们的任何左值。
template <typename F>
inline constexpr decltype(auto)
makeFixPoint(F&& f) noexcept
{
return FixPoint<std::decay_t<F>>{std::forward<std::decay_t<F>>(f)};
}
好的,这里有更多危险信号。 inline constexpr
是废话的标志; constexpr
函数总是 inline
。可能有些编译器将额外的 inline
视为某种意义,因此不能保证一定会出现问题。
return FixPoint<std::decay_t<F>>{std::forward<std::decay_t<F>>(f)};
std::forward
是有条件的着法。如果有右值或值类型传递给它,它就会移动。 decay
保证 return 一个值类型。所以我们只是去掉了操作的条件部分,把它变成了一个原始的移动。
return FixPoint<std::decay_t<F>>{std::move(f)};
这个比较简单,意思一样。
此时你可能会看到危险:
template <typename F>
constexpr decltype(auto)
makeFixPoint(F&& f) noexcept
{
return FixPoint<std::decay_t<F>>{std::move(f)};
}
我们无条件地从 makeFixPoint
论点转移。名称 makeFixPoint
没有任何内容表示“我从参数中移出”,并且它接受转发引用;所以它会默默地消耗一个左值和move-from它。
这很粗鲁。
所以代码的合理版本是:
template <typename F>
class FixPoint : private F
{
public:
template<class U,
class dU = std::decay_t<U>,
std::enable_if_t<!std::is_same_v<dU, FixPoint> && std::is_same_v<dU, F>, bool> = true
>
explicit constexpr FixPoint(U&& u) noexcept
: F{std::forward<U>(u)}
{}
[截图]
namespace
{
template <typename F>
constexpr FixPoint<std::decay_t<F>>
makeFixPoint(F&& f) noexcept
{
return FixPoint<std::decay_t<F>>{std::forward<F>(f)};
}
} // namespace
稍微清理一下。