在下面的代码中,编译器似乎有时更喜欢调用模板化的构造函数,并且在复制构造函数应该很好的时候无法编译。根据值被捕获为v还是v= v,行为似乎会发生变化,我认为它们应该是完全相同的。我遗漏了什么?
我使用gcc 11.2.0并用"g++ file.cpp -std=C++17“编译它
#include <functional>
#include <iostream>
#include <string>
using namespace std;
template <class T>
struct record {
explicit record(const T& v) : value(v) {}
record(const record& other) = default;
record(record&& other) = default;
template <class U>
record(U&& v) : value(forward<U>(v)) {} // Removing out this constructor fixes print1
string value;
};
void call(const std::function<void()>& func) { func(); }
void print1(const record<string>& v) {
call([v]() { cout << v.value << endl; }); // This does not compile, why?
}
void print2(const record<string>& v) {
call([v = v]() { cout << v.value << endl; }); // this compiles fine
}
int main() {
record<string> v("yo");
print1(v);
return 0;
}
发布于 2022-07-06 08:01:18
我不反对康桓瑋的回答,但我觉得有点难理解,所以让我用另一个例子来解释它。
#include <functional>
#include <iostream>
#include <typeinfo>
#include <type_traits>
struct tracer {
tracer() { std::cout << "default constructed\n"; }
tracer(const tracer &) { std::cout << "copy constructed\n"; }
tracer(tracer &&) { std::cout << "move constructed\n"; }
template<typename T> tracer(T &&t) {
if constexpr (std::is_same_v<T, const tracer>)
std::cout << "template constructed (const rvalue)\n";
else if constexpr (std::is_same_v<T, tracer&>)
std::cout << "template constructed (lvalue)\n";
else
std::cout << "template constructed (other ["
<< typeid(T).name() << "])\n";
}
};
int
main()
{
using fn_t = std::function<void()>;
const tracer t;
std::cout << "==== value capture ====\n";
fn_t([t]() {});
std::cout << "==== init capture ====\n";
fn_t([t = t]() {});
}
运行时,此程序输出以下内容:
default constructed
==== value capture ====
copy constructed
template constructed (const rvalue)
==== init capture ====
copy constructed
move constructed
这是怎么回事?首先,在这两种情况下,编译器必须实现一个临时lambda对象,以传递给fn_t
的构造函数。然后,fn_t
的构造函数必须复制lambda对象才能保持它。(通常情况下,std::function
可能比传递给其构造函数的lambda更长时间,因此它不能仅通过引用保留lambda。)
在第一种情况下(值捕获),捕获的t
的类型恰好是t
的类型,即const tracer
。因此,您可以将lambda对象的未命名类型看作某种编译器定义的struct
,其中包含一个类型为const tracer
的字段。让我们给这个结构一个假的LAMBDA_T
名称。因此,fn_t
构造函数的参数是LAMBDA_T&&
类型,因此访问内部字段的表达式是const tracer&&
类型,它比实际的复制构造函数更符合模板构造函数的转发引用。(在重载解析中,当rvalue可用时,rvalue比绑定到const lvalue引用更倾向于绑定。)
在第二种情况下(init ),捕获的t = t
的类型相当于auto tnew = t
这样的声明中的tnew
类型,即tracer
。因此,现在我们内部LAMBDA_T
结构中的字段将是tracer
类型而不是const tracer
类型,当必须移动复制LAMBDA_T&&
类型的参数到fn_t
构造函数时,编译器将选择tracer
的正常移动构造函数来移动该字段。
发布于 2022-07-06 04:22:27
对于[v]
,lambda内部成员变量v
的类型是const record
,所以当您
void call(const std::function<void()>&);
void print1(const record<string>& v) {
call([v] { });
}
因为[v] {}
是一个prvalue,所以当它初始化const std::function&
时,v
将被const record&&
复制,模板构造函数将被选择,因为它不受约束。
为了调用v
的复制构造函数,您可以
void call(const std::function<void()>&);
void print1(const record<string>& v) {
auto l = [v] { };
call(l);
}
对于[v=v]
,lambda中的成员变量v
的类型是record
,所以当prvalue lambda初始化std::function
时,它将直接调用record
的移动构造函数,因为record&&
更匹配。
https://stackoverflow.com/questions/72877471
复制相似问题