实现std::move基本上如下:
template<typename T>
typename std::remove_reference<T>::type&&
move(T&& t)
{
return static_cast<typename std::remove_reference<T>::type&&>(t);
}
Run Code Online (Sandbox Code Playgroud)
请注意,参数std::move是通用引用(也称为转发引用,但我们不在此处转发).也就是说,你可以std::move使用左值和左值:
std::string a, b, c;
// ...
foo(std::move(a)); // fine, a is an lvalue
foo(std::move(b + c)); // nonsense, b + c is already an rvalue
Run Code Online (Sandbox Code Playgroud)
但是,由于整个观点std::move都是为了施展到一个rvalue,为什么我们甚至允许std::movervalues呢?如果std::move只接受左值,那会不会更有意义?
template<typename T>
T&&
move(T& t)
{
return static_cast<T&&>(t);
}
Run Code Online (Sandbox Code Playgroud)
然后,无意义的表达式std::move(b + c)将导致编译时错误.
std::move对于初学者来说,上面的实现也会更容易理解,因为代码完全按照它的样子执行:它需要一个左值并返回一个右值.您不必了解通用引用,引用折叠和元函数.
那么为什么std::move设计同时采用左值和左值?
我想开发一个带有类型擦除的小型多态类,我想知道哪个版本的模板化构造函数更好,应该使用.
我们可以通过价值:
class A
{
...
template< typename T >
A( T t ) { /* create the underlying model via std::move */ }
...
};
Run Code Online (Sandbox Code Playgroud)
或者我们可以使用通用参考:
class A
{
...
template< typename T >
A( T &&t ) { /* create the underlying model via std::forward */ }
...
};
Run Code Online (Sandbox Code Playgroud)
(如果对于T不是类本身并且未复制类的情况,则必须启用通用引用).有任何想法吗?这两个版本看起来都和我一样.
c++ pass-by-value pass-by-rvalue-reference forwarding-reference
在C++ 17中,可以在不指定模板类型的情况下实例化对象.基本上,这段代码会编译:
std::pair p(2, 4.5); // deduces to std::pair<int, double> p(2, 4.5);
std::tuple t(4, 3, 2.5); // same as auto t = std::make_tuple(4, 3, 2.5);
Run Code Online (Sandbox Code Playgroud)
所以,假设以下代码:
template<typename... Ts>
struct Foo
{
Foo(Ts&&... ts) :
ts{std::forward_as_tuple(ts...)}
{}
std::tuple<Ts...> ts;
};
int main()
{
auto f = [] { return 42; };
Foo foo{f, [] { return 84; }};
}
Run Code Online (Sandbox Code Playgroud)
我应该std::decay在这样的元组声明中使用吗?
std::tuple<std::decay_t<Ts>...> ts;
Run Code Online (Sandbox Code Playgroud)
因为这是我根据推导出的模板类型编写函数来返回对象的方法:
template<typename T>
auto make_baz(T&& t) -> baz<std::decay_t<T>>;
Run Code Online (Sandbox Code Playgroud)
我可以在Foo的构造函数中看到这种模式,它使用转发引用将值正确地传递给元组.我不确定这里的类型演绎是否表现相同.
我有一个类对象,它采用仅限于某个概念的通用参数。现在我想将此参数(哪种类型仍然未知)转发到construct适合此引用类型的重载:根据转发的参数选择 rvalue-ref 重载或 const ref 重载。我怎样才能做到这一点?
问题是,一旦我输入双与号,“模板化右值引用”就成为通用引用,我看不到一种方法来规避它。下面的示例显示,在这两种情况下,都会选择“贪婪”通用引用重载,即使我希望第一个实例化与 const ref 一起使用。
#include <concepts>
#include <cstdio>
#include <utility>
template <typename... Args>
struct function
{
template <std::invocable<Args...> Cb>
function(Cb&& fn) {
construct(std::forward<Cb>(fn));
}
template<typename Cb>
auto construct(const Cb&) {
printf("Overload for const-ref called!\n");
}
template <typename Cb>
auto construct(Cb&&) {
printf("Overload for rvalue-ref called!\n");
}
};
struct functor
{
auto operator()()
{
printf("Functor called!\n");
}
};
int main()
{
functor foo1;
function myfunc{foo1};
function myfunc2{functor{}};
}
Run Code Online (Sandbox Code Playgroud)
输出:
Overload for rvalue-ref …Run Code Online (Sandbox Code Playgroud) 在c ++ 11之前,我曾经写过这样的代码:
// Small functions
void doThingsWithA(const A& a)
{
// do stuff
}
void doThingsWithB(const B& b)
{
// do stuff
}
void doThingsWithC(const C& c)
{
// do stuff
}
// Big function
void doThingsWithABC(const A& a, const B& b, const C& c)
{
// do stuff
doThingsWithA(a);
doThingsWithB(b);
doThingsWithC(c);
// do stuff
}
Run Code Online (Sandbox Code Playgroud)
但是现在,使用移动语义,允许我的函数将rvalue引用作为参数并添加这些重载可能会变得有趣(至少在某些情况下):
void doThingsWithA(A&& a);
void doThingsWithB(B&& b);
void doThingsWithC(C&& c);
Run Code Online (Sandbox Code Playgroud)
从我收集的内容来看,如果我想在我的大函数中调用那些重载,我需要使用完美的转发,这可能看起来像这样(它的可读性稍差,但我想它可以用模板类型的良好命名约定):
template<typename TplA, typename TplB, typename TplC>
void doThingsWithABC(TplA&& a, TplB&& b, TplC&& …Run Code Online (Sandbox Code Playgroud) c++ perfect-forwarding c++11 universal-reference forwarding-reference
假设我有一个模板类
template <typename T> class foo;
template <typename... Args>
struct foo<std::tuple<Args...>> {
std::tuple<Args...> t;
foo(Args&&... args): t{std::forward<Args>(args)...} { }
};
Run Code Online (Sandbox Code Playgroud)
我知道在这种情况下Args&&...是rvalue引用,我可以编写代码std::move而不是std::forward.
我也可以有一个带左值引用的构造函数,就像这样
foo(const Args&... args): t{args...} { }
Run Code Online (Sandbox Code Playgroud)
问题是,是否可以获得与转发引用相同的行为,但对于确定类型?我想要这个的原因是我可以使用语法
foo bar({. . .}, {. . .}, . . ., {. . .});
Run Code Online (Sandbox Code Playgroud)
如果我定义foo(Args&&... args)构造函数,但是不允许混合场景,我可以使用大括号括起初始化列表初始化一些成员元组元素,并从原有的对象实例中复制其他元素.
使用函数,可以编写:
template <class T> void f(T&& x) {myfunction(std::forward<T>(x));}
Run Code Online (Sandbox Code Playgroud)
但是有了lambda,我们没有T:
auto f = [](auto&& x){myfunction(std::forward</*?*/>(x));}
Run Code Online (Sandbox Code Playgroud)
如何在lambda中完美转发?是否decltype(x)可以作为类型std::forward?
Scott Meyers 所著的《Effective Modern C++》一书中给出了“避免通用引用重载”的建议(第 26/27 项)。他的理由是,在几乎所有对包含通用引用的重载函数的调用中,编译器都会解析为通用引用,即使这通常不是您想要解析的函数。(所以我认为这段代码很糟糕?)
template <typename T>
void foo(T&& t) {
// some sort of perfect forwarding
}
void foo(string&& t) {
// some sort of move operation
}
Run Code Online (Sandbox Code Playgroud)
上面的例子是精心设计的,可能会被替换为 2 个函数。
我认为更难解决且不那么做作的另一个例子是他在第 26 项中实际给出的例子。
class Foo {
// a decent amount of private data that would take a while to copy
public:
// perfect forwarding constructor, usually the compiler resolves to this...
template <typename T>
explicit Foo(T&& t) : /* forward the information for construction */ …Run Code Online (Sandbox Code Playgroud) 在这样的函数模板中
template <typename T>
void foo(T&& x) {
bar(std::forward<T>(x));
}
Run Code Online (Sandbox Code Playgroud)
如果用右值引用调用,x内部不是右值引用吗?如果 foo 是用左值引用调用的,则无论如何都不需要强制转换,因为在. 也将被推导为左值引用类型,因此不会改变.foofooxfooTstd::forward<T>x
我使用boost::typeindex和不使用std::forward<T>.
#include <iostream>
#include <utility>
#include <boost/type_index.hpp>
using std::cout;
using std::endl;
template <typename T> struct __ { };
template <typename T> struct prt_type { };
template <typename T>
std::ostream& operator<<(std::ostream& os, prt_type<T>) {
os << "\033[1;35m" << boost::typeindex::type_id<T>().pretty_name()
<< "\033[0m";
return os;
}
template <typename T>
void foo(T&& x) {
cout << prt_type<__<T>>{} …Run Code Online (Sandbox Code Playgroud) c++ move-semantics perfect-forwarding c++11 forwarding-reference
下面的代码标准正确吗?(天马行空)
即 by-ref 捕获表示临时的转发引用,并在同一表达式中从函数返回结果 lambda 值。
当然,存储 lambda 以供以后使用会使它包含一个悬空引用,但我指的是main.
我的疑虑与这个 SO answer和潜在的这种语言缺陷有关。具体来说,有一个令人生畏的评论说“标准引用中的引用捕获生命周期规则捕获了变量,而不是数据及其范围” ——这似乎是说捕获的临时引用在我的代码中可能是无效的。
#include <stdlib.h>
#include <string.h>
#include <cassert>
template<typename F>
auto invoke(F&& f)
{
return f();
}
template<typename F>
auto wrap(F&& f)
{
return [&f]() {return f();}; // <- this by-ref capture here
}
int main()
{
int t = invoke(wrap(
[]() {return 17;}
));
assert(t == 17);
return t;
}
Run Code Online (Sandbox Code Playgroud) c++ ×10
c++11 ×3
c++14 ×2
lambda ×2
c++-concepts ×1
c++17 ×1
c++20 ×1
decltype ×1
overloading ×1
rvalue ×1
template-argument-deduction ×1
templates ×1