我想理解为什么const volatile引用不能绑定到右值引用?禁止这种转换的理由是什么?
在下面的代码中,我注释掉了不编译的行:
int main(){
int i=1;
//const volatile int& cvi2=std::move(i); -> ERROR: why?
const volatile int i2=0;
//const volatile int& cvi3=std::move(i2);// -> ERROR: why?
}
Run Code Online (Sandbox Code Playgroud)
这是一个更现实的场景,由于类似的原因无法编译:
#include<iostream>
template<class T> void g(const T& b){
//do usefull things
}
template<class T,class F> void f(T& a,F a_func){
//do usefull things with a
a_func(std::move(a));
}
int main(){
int i=0;
volatile int vi=1;
f(i,g<int>); //OK no error;
f(vi,g<volatile int>);//ERROR: can not convert volatile int to
//const volatile int &
}
Run Code Online (Sandbox Code Playgroud)
在这段代码中,我希望g<volatile int>(const volatile&) …
在哪个线程中称为终止处理程序:
当noexcept函数内部抛出异常时?
当用户调用std::terminate()?
在启动或破坏thread?
它是否在标准中定义,我是否可以访问thread_local对象?
考虑以下这段代码:
struct S{
int i;
S(int);
S(const volatile S&);
};
struct S_bad{
int i;
};
volatile S as{0};
volatile S_bad as_bad{0};
volatile int ai{0};
void test(){
ai; //(1)=> a load is always performed
as; //(2)=> Should call the volatile copy constructor
as_bad; //(3)=> Should be ill-formed
}
Run Code Online (Sandbox Code Playgroud)
表达式ai;,as;并且as_bad是废弃的值表达式,并根据C++草案标准N4659/[expr] .12我预计在这三种情况下将应用左值到右值.对于情况(2),这应该导致对volatile复制构造函数(S(const volatile S&))[expr]/12的调用
[...]如果表达式是此可选转换后的prvalue,则应用临时实现转换([conv.rval]).[注意:如果表达式是类类型的左值,则它必须具有易失性复制构造函数来初始化临时值,该临时值是左值到右值转换的结果对象. - 结束说明]
因此案例(3)应该是不正确的.
然而,编译器的行为似乎很混乱:
GCC:
ai;=>加载值ai;as; =>没有代码生成,没有警告;as_bad;=>加载as_bad.i …在[dcl.init] /17.6中,明确写出,对于括号初始化的情况,会发生复制省略:
如果初始化表达式是prvalue且源类型的cv-nonqualified版本与目标类相同,则初始化表达式用于初始化目标对象.[ 例子: T x = T(T(T())); 调用T默认构造函数来初始化x.- 结束例子 ]
但是在列表初始化的情况下,上面的段落不适用,我没有发现任何类似的东西.见[dcl.init.list].
那么为什么在这种情况下有复制省略:T x{T(T())};根据C++ 17标准.
两个不同的类模板部分特化声明何时匹配?
在下面的代码中有两个部分特化声明:
S<constrain<T,has_accept_>, void>S<constrain<T,has_visit_>, void>constrain是一个别名模板,它等于T但是使用enable_if第二个参数作为概念的技巧进行约束.
GCC认为这两个部分专业化是不同的,但Clang和MSVC认为它们是等价的,因此拒绝代码:
#include <type_traits>
#include <utility>
using namespace std;
template<class T,class=void>
struct has_accept
:false_type{};
template<class T>
struct has_accept<T,void_t<decltype(declval<const T&>().accept())>>
:true_type{};
template<class T,class=void>
struct has_visit
:false_type{};
template<class T>
struct has_visit<T,void_t<decltype(declval<const T&>().visit())>>
:true_type{};
//pre c++17 clang/MSVC fix: default argument of template
// used as template template argument not implemented yet
template<class T> using has_accept_ = has_accept<T>;
template<class T> using has_visit_ = has_visit<T>;
template<class T,template<class> class TT,class=enable_if_t<TT<T>::value>>
using …Run Code Online (Sandbox Code Playgroud) 这个问题followes这一个
我们来考虑这个示例代码:
struct sso
{
union{
struct {
char* ptr;
char size_r[8];
} large_str;
char short_str[16];
};
bool is_short_str() const{
return *std::launder(short_str+15)=='\0'; //UB?
}
};
Run Code Online (Sandbox Code Playgroud)
如果short_str不是取消引用指针的活动成员而std::launder不是UB.让我们考虑ABI已经明确指定,并且我们知道size_r [7]与short_str [15]位于同一地址.并std::launder(short_str+15)返回一个指针size_r[7]的时候short_str是不是工会的活跃成员?
Nota:我认为情况就是这样,因为[ptr.launder]/3
如果对象Y位于Y所占用的存储区内,则指向存储的字节可以到达,如果Y是指针可互换的对象,则指向对象Y,或者如果Y是数组元素,则指向立即封闭的数组对象.
作为这个问题的后续,我测试了clang和gcc的行为.似乎两个编译器对c ++标准有不同的解释.
在下面的示例中,如果需要根据推理指南假设的构造函数参数复制不可复制的参数,则GCC拒绝编译.Clang不执行此检查:
#include <cstddef>
struct not_copyable{
not_copyable()=default;
not_copyable(const not_copyable&)=delete;
};
struct movable{
movable()=default;
movable(movable&&);
};
template <typename T, size_t N>
struct A
{ template <typename ... Ts> A (Ts const & ...) {} };
template <typename T, size_t N>
struct B
{ template <typename ... Ts> B (const Ts & ...) {} };
template <typename T, typename ... Ts>
A(T const &, Ts const & ...) -> A<T, 1U + sizeof...(Ts)>;
template <typename T, typename ... Ts> …Run Code Online (Sandbox Code Playgroud) 在C++ ISO标准N4618中(但它几乎也适用于C++ 11版本),可以阅读:
在§1.8C++对象模型:
如果在与
e"N unsigned char数组"类型的另一个对象关联的存储中创建了一个完整对象(5.3.4),该数组为创建的对象提供存储... [注意:如果该数组的那部分先前提供了存储对于另一个对象,该对象的生命周期因为其存储被重用而结束]
=>好的,unsigned char数组可以为其他对象提供存储空间.如果新对象占用了之前被其他对象占用的存储空间,则新对象会重用前一个对象的存储空间.
在§3.8.8对象生命周期
如果在对象的生命周期结束之后并且在重用或释放对象占用的存储之前,则在原始对象占用的存储位置处创建新对象,...
=>我可以在另一个对象的存储位置构造一个对象,但是这个操作不是"存储重用"(否则为什么要写入...在对象占用的存储被重用之前......)
并作为§3.8.8的一个例子
struct C {
int i;
void f();
const C& operator=( const C& );
};
const C& C::operator=( const C& other) {
if ( this != &other ) {
this->~C(); // lifetime of *this ends
new (this) C(other); // new object of type C created
f(); // well-defined
}
return *this;
}
C c1;
C c2;
c1 …Run Code Online (Sandbox Code Playgroud) 考虑这个联合:
union A{
int a;
struct{
int b;
} c;
};
Run Code Online (Sandbox Code Playgroud)
c并且a不是布局兼容类型,因此无法读取bthrough 的值a:
A x;
x.c.b=10;
x.a+x.a; //undefined behaviour (UB)
Run Code Online (Sandbox Code Playgroud)
对于审判1和审判2,请参阅此问题
现在让我们使用std::launder它似乎不想要的东西:
A x;
x.a=10;
auto p = &x.a; //(1)
x.c.b=12; //(2)
p = std::launder(p); //(2')
*p+*p; //(3) UB?
Run Code Online (Sandbox Code Playgroud)
可以std::launder改变什么吗?根据[ptr.launder]:
template <class T> constexpr T* launder(T* p) noexcept;需要:
p表示内存中一个字节的地址A。在其生存期内且类型相似的对象XT位于地址A处。通过结果可以访问的所有存储字节都是可以访问的p(请参见下文)。返回: …
如P0532R0中所述,在下面的用例中std::launder必须使用以避免未定义的行为(UB):
struct X{
const int i;
x(int i):i{i}{}
};
unsigned char buff[64];
auto p = new(buff) X(33);
p->~X();
new(buff) X(42);
p = std::launder(p);
assert(p->i==42);
Run Code Online (Sandbox Code Playgroud)
但是在缓冲区中有多个对象的情况下会发生什么(如果X在向量中推送2 ,则会发生这种情况,清除向量然后再推送两个对象X):
unsigned char buff[64];
auto p0 = new(buff) X(33);
auto p1 = new(p0+1) X(34);
p1->~X();
p0->~X();
new(buff) X(42);
new(p0+1) X(43);
p0 = std::launder(p0);
assert(p0->i==42);
assert(p0[1].i==43);//???
Run Code Online (Sandbox Code Playgroud)
最后一个断言是正确的,还是p0[1]仍然调用UB?
c++ ×10
c++17 ×7
c++11 ×2
pointers ×2
copy-elision ×1
template-argument-deduction ×1
terminate ×1
unions ×1
volatile ×1