这是我之前的问题的后续问题,其中明显的共识是,prvalues的cv资格处理的变化只是一个相当小的和无关紧要的变化,旨在解决一些不一致性(例如返回prvalues并通过cv-qualified返回声明的函数)类型).
但是,我看到标准中的另一个地方似乎依赖于具有cv限定类型的constprvalues:通过临时实现转换初始化带有prvalues 的引用.有关措辞可在9.3.3/5中的多个地点找到
[...]如果转换的初始化程序是prvalue,则将其类型T4调整为键入"cv1 T4"([conv.qual])并应用临时实现转换([conv.rval])[...]
[...]否则,初始化表达式被隐式转换为"cv1 T1"类型的prvalue.应用临时实现转换,并将引用绑定到结果.
目的很明显是当我们进行实际的临时物化转换时
7.3.4临时实现转换
1类型T的prvalue可以转换为类型T的xvalue.此转换通过使用临时对象评估prvalue,从prvalue初始化类型为T的临时对象([class.temporary])它的结果对象,并生成一个表示临时对象的xvalue.[...]
T它作为输入接收的类型包括所需的cv资格.
但是在非类非数组prvalue的情况下,该cv资格如何在7.2.2/2中存活?
7.2.2类型
2如果prvalue最初具有类型"cv T",其中T是cv非限定的非类非数组类型,则在进行任何进一步分析之前将表达式的类型调整为T.
或者是吗?
例如,我们在这个例子中得到了什么样的临时性
const int &r = 42;
Run Code Online (Sandbox Code Playgroud)
暂时const与否?我们能做到吗
const_cast<int &>(r) = 101; // Undefined or not?
Run Code Online (Sandbox Code Playgroud)
没有触发未定义的行为?如果我没有弄错的话,最初的意图是const int在这种情况下获得临时性.它仍然是真的吗?(对于班级类型,答案很清楚 - 我们得到一个const临时的.)
使用c ++ 17,我们看到了新的is_invocable和花哨的新prvalues,它们并不是真正的价值.
这允许您创建一个对象而无需首先在逻辑上构造它,然后忽略构造.
我遇到了一个问题,std::is_invocable用于测试你是否可以调用某些内容,而prvalue规则似乎会发生冲突:
struct no_move {
no_move(no_move&&)=delete;
explicit no_move(int) {}
};
void f( no_move ) {}
Run Code Online (Sandbox Code Playgroud)
现在我们可以问一下是否f可以使用类型的prvalue调用no_move?
f( no_move(1) )
Run Code Online (Sandbox Code Playgroud)
std::is_invocable< decltype(&f), no_move >不起作用,因为它使用的std::declval<no_move>()是xvalue,no_move&&而不是类型的prvalue no_move.
在c ++ 14中,这是相同的,但保证省略使得一些函数可以用xvalue(即" T&&")调用,而其他函数可以用prvalues类型调用T.
有替代方案,还是我们必须发明自己的特性来处理这种情况?
(在一个理论的世界里std::declval<T>返回T,而不是T&&,is_invocable会的,我相信,做正确的事).
我对这个问题的广泛性感到抱歉,只是所有这些细节都是紧密相连的。
我一直在尝试理解两个特定值类别 - xvalues 和 prvalues 之间的区别,但我仍然感到困惑。
不管怎样,我试图为自己开发的“同一性”概念的心智模型是,应该保证具有同一性的表达式驻留在实际程序的数据存储器中。
由于这个原因,字符串文字是左值,它们保证在整个程序运行期间驻留在内存中,而数字文字是纯右值,并且可以假设存储在直接汇编中。
这似乎同样适用于std::move纯纯右值文字,即,在调用时,fun(1)我们只会在被调用者框架中获得参数左值,但在调用时,fun(std::move(1))左值的 xvalue“种类”必须保留在调用者框架中。
然而,这种心理模型至少不适用于临时对象,据我所知,临时对象应该始终在实际内存中创建(例如,如果像使用纯右值参数那样调用fun(MyClass())右值引用函数)。所以我认为这种思维模式是错误的。
那么,思考 xvalue 的“同一性”属性的正确方法是什么?我已经读过,通过身份我可以比较地址,但是如果我可以比较 2 的地址MyClass().member(根据 cppreference 的 xvalue),比方说通过右值引用将它们传递到某个比较函数中,那么我不明白为什么我可以不对 2 MyClass()s(纯右值)做同样的事情吗?
与此相关的另一个来源是此处的答案: 什么是移动语义?
请注意,尽管 std::move(a) 是右值,但其求值不会创建临时对象。这个难题迫使委员会引入第三个价值类别。可以绑定到右值引用的东西,即使它不是传统意义上的右值,也称为xvalue(到期值)。
但这似乎与“可以比较地址”无关,并且a)我不明白这与右值的“传统意义上”有什么不同;b) 我不明白为什么这样的原因需要语言中的新值类别(好吧,这允许为 OO 意义上的对象提供动态类型,但 xvalue 不仅仅指对象)。
我用以下最终结果错误地粘贴了一个throw声明return:
void DXManager::initialize(const std::shared_ptr<nae::Context>& ctx_ptr)
{
// ...
if (FAILED(result))
{
return throw std::exception("Failed to enumerate display mode list");
}
// ...
}
Run Code Online (Sandbox Code Playgroud)
我在注意到错误之前成功构建了解决方案,我很好奇哪个规范允许上面的语法.
通过阅读cppreference.com(在Notes下),我明白了
throw-expression被归类为void类型的prvalue表达式.与任何其他表达式一样,它可能是另一个表达式中的子表达式,最常见于条件运算符中:
Run Code Online (Sandbox Code Playgroud)double f(double d) { return d > 1e7 ? throw std::overflow_error("too big") : d; } // ...
但我不太确定这是我在找什么.
在§[except.throw],标准中说抛出异常拷贝 - 从throw表达式初始化异常对象
抛出异常copy-initializes(11.6,15.8)一个临时对象,称为异常对象
为什么以下代码在C++ 17上编译?
class Exception {
public:
Exception() = default;
Exception(Exception&&) = delete;
Exception(const Exception&) = delete;
};
int main() {
throw Exception{};
return 0;
}
Run Code Online (Sandbox Code Playgroud)
(https://wandbox.org/permlink/R3WfzfnBAORTLVSy)
复制初始化不包括符合prvalue elision条件的任何情况(从我的看来).为什么上面的代码在C++ 17中编译?
假设我们有一些不可复制和不可移动的类型Foo,以及一个函数
int f(const Foo& foo) {
... // somehow compute the result depending on the value of foo.
}
Run Code Online (Sandbox Code Playgroud)
现在假设我们想像这样调用这个函数:
const Foo foo1{ 42 };
bool condition = getCondition();
int result = f(condition ? foo : Foo{ 150 });
Run Code Online (Sandbox Code Playgroud)
这不会编译,因为foo和Foo{ 150 }属于不同的值类别,因此,根据定义,三元运算符将返回类型的纯右值Foo- 这意味着foo1需要复制 if condition == true,因此这会导致编译错误。
然而,C++ 保证临时对象在表达式结束之前保持有效,因此我们可以这样写:
const Foo foo1{ 42 };
bool condition = getCondition();
int result = f(condition ? foo1 : static_cast<const Foo&>(Foo{ 150 }); …Run Code Online (Sandbox Code Playgroud) 我试图用C++做到这一点:
class Abc
{
int callFunction1()
};
void function1(Abc** c1) {//do something}
int Abc::callFunction1()
{
function1(&this);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
而且我在visual studio 2015中得到"表达式必须是l值或函数指示符"错误.所以我不明白我哪里出错了.据我所知,&this该类型应该Abc**对吗?
函数定义不是我要改变的.所以我不能只改变参数类型.
考虑以下代码:
struct S
{
S(int, double) {}
explicit S(const S&) {}
explicit S(S&&) {}
};
void i_take_an_S(S s) {}
S i_return_an_S() { return S{ 4, 2.0 }; }
int main()
{
i_take_an_S(i_return_an_S());
}
Run Code Online (Sandbox Code Playgroud)
使用“-std=c++17”标志,g++ 和 clang++ 都可以很好地编译此代码。然而,MSVC(带有 /std:c++17 标志)报告
“错误 C2664:'void i_take_an_S(S)':无法将参数 1 从 'S' 转换为 'S'”
作为编译错误,带有附加注释
“结构‘S’的构造函数被声明为‘显式’”。
根据C++17的初始化规则(第3点的解释)S,不应考虑使用复制构造函数来S初始化i_take_an_S;S(int, double)应该通过直接列表初始化选择精确匹配。
这可能是 MSVC 中的一个错误吗?
首先,我听说“保证复制消除”是一个用词不当(据我目前的理解,它更多的是从根本上重新定义基本值类别,从 r/l 值到 l/x/pr 值,这从根本上改变了含义和副本的要求),但由于这就是通常所说的,我也会。
在阅读了有关这个主题的一些内容后,我想我终于理解了它 - 至少足以认为:
my_vector.push_back({arg1, arg2});
Run Code Online (Sandbox Code Playgroud)
从 c++17 开始,相当于:
my_vector.emplace_back(arg1, arg2);
Run Code Online (Sandbox Code Playgroud)
我最近试图说服我的同事这一点。唯一的问题是他告诉我我完全错了!他编写了一些 godbolt 代码(如下所示),其中程序集显示push_back创建了一个临时文件,该临时文件被移入向量中。
因此,为了完成我的问题,我必须首先证明这里存在一些令人困惑的原因。我将引用关于这个主题的备受推崇的 stackoverflow 答案(强调我的):
保证复制省略重新定义了纯右值表达式的含义。[...]纯右值表达式只是可以实现临时值的东西, 但它还不是临时值。
如果使用纯右值来初始化该纯右值类型的对象, 则不会具体化临时值。[...]
需要理解的是,由于返回值是纯右值, 因此它还不是对象。它只是一个对象的初始化器[...]
就我而言,我认为auto il = {arg1, arg2}会调用 for 的构造函数std::initializer_list,但{arg1, arg2}inpush_back({arg1, arg2})将是纯右值(因为它未命名),因此将是向量元素的初始化器,而无需对其进行初始化。
当你执行 T t = Func(); 时,返回值的纯右值直接初始化对象 t;没有“创建临时和复制/移动”阶段。由于 Func() 的返回值是与 T() 等效的纯右值,因此 t 直接由 T() 初始化,就像执行 T t = T() 一样。
如果以任何其他方式使用纯右值,则纯右值将具体化一个临时对象,该对象将在该表达式中使用(如果没有表达式,则将其丢弃)。因此,如果您执行 const T &rt = Func();,纯右值将实现一个临时值(使用 …
我在理解类型何时推导出std::ranges::dangling何时使用命名局部变量与纯右值作为std::ranges::sort算法的参数时遇到问题。例如,我有一个函数将 a 返回prvalue 到标准容器,比如 std::vector ,我直接将其用作 的参数std::ranges::sort,然后我期望std::ranges::dangling在尝试取消引用迭代器时收到编译错误,这就是我得到的:
#include <vector>
#include <algorithm>
auto get_data(){
return std::vector<int>{1, 2, 99, 5, 9, 4};
}
auto get_sorted(){
return std::ranges::sort(get_data());
}
int main(){
auto it = get_sorted();
*(it-1); //Compiler Error because it is a std::ranges::dangling iterator
}
Run Code Online (Sandbox Code Playgroud)
但是,如果我稍微更改get_sorted上面的函数以首先捕获命名变量中的向量并使用它来返回 的结果std::ranges::sort,那么我不会得到悬空迭代器,即使main命名变量中分配的向量应该是函数get_sorted返回后销毁:
auto get_sorted(){
auto vec = get_data();
return std::ranges::sort(vec);
}
int main(){
auto it = get_sorted();
*(it-1); //Okay result = …Run Code Online (Sandbox Code Playgroud) 遵循这个问题的公认答案Do rvalue references allow dangling references?当分配给右值引用左值时,xvalues 的生命周期似乎没有延长,就像问题中一样。但是,当我这样做时
#include <iostream>
using namespace std;
class Something {
public:
Something() {
cout << "Something()" << endl;
}
Something(const Something&) {
cout << "Something(const Something&)" << endl;
}
Something(Something&&) {
cout << "Something(Something&&)" << endl;
}
~Something() {
cout << "~Something()" << endl;
}
int a;
};
Something make_something() {
return Something{};
}
int main() {
auto&& something = make_something().a;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
调用返回的对象的生命周期make_something被延长,即使make_something().a是根据http://en.cppreference.com/w/cpp/language/value_category的 xvalue (xvalues …
cppreference 表示:当引用绑定到纯右值时,会创建一个临时对象。它们是指 const 左值引用和右值引用吗?:
当纯右值具体化时,会创建临时对象,以便将其用作左值,这会在以下情况下发生 (C++17 起):
- 将引用绑定到纯右值
如果他们的意思是这样,绑定到相同类型纯右值的右值引用和常量左值引用是否会创建临时值?我的意思是,这是否正在发生:
const int &x = 10; // does this creates temporary?
int &&x2 = 10; // does this creates temporary?
Run Code Online (Sandbox Code Playgroud) decltype(1 + 2)是否声明了xvalue或prvalue?
cppreference说,decltype(表达式)将声明:1.T && if表达式是否是xvalue 2.如果表达式是prvalue 3. T&if表达式是lvalue
但我的问题是:如何生成一个xvalue的表达式?我想返回值和临时对象应该是xvalue,但实际上它们似乎是xvalue,在我的实验中:
struct S{};
S f();
int main()
{
int i=2;
decltype(i+1) j=i;
++j;
printf("i=%d\n",i);
S obj;
decltype(f()) k=obj;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这个程序编译:我可以判断
decltype(i + 1)将(i + 1)声明为prvalue
因为如果它是一个xvalue,那么decltype得到T &&,它不能绑定到左值变量"i".decltype(f())也给我f()作为prvalue也很奇怪?
所以我的问题是:如何写一个表达式,以便decltype(表达式)给我一个xvalue?谢谢.