(参考这个问题和答案.)
在C++ 17标准之前,[basic.compound]/3中包含以下句子:
如果类型T的对象位于地址A,则类型为cv T*的指针(其值为地址A)被称为指向该对象,而不管该值是如何获得的.
但是自从C++ 17以来,这句话已被删除.
例如,我相信这句话使这个示例代码定义,并且从C++ 17开始这是未定义的行为:
alignas(int) unsigned char buffer[2*sizeof(int)];
auto p1=new(buffer) int{};
auto p2=new(p1+1) int{};
*(p1+1)=10;
Run Code Online (Sandbox Code Playgroud)
在C++ 17之前,p1+1保持地址*p2并具有正确的类型,因此*(p1+1)是指向*p2.在C++中,17 p1+1是一个指向前端的指针,所以它不是指向对象的指针,我相信它不是可以解除引用的.
对标准权的这种修改的解释是否还有其他规则来补偿所引用的句子的删除?
考虑这段代码(演示):
#include <tuple>
#include <type_traits>
struct Ag{int i;int j;};
using T = std::tuple<int,int>;
using Ar = int[2];
const Ag ag {};
const T t {};
const Ar ar {};
void bind_ag(){
auto [i,j] = ag;
static_assert(std::is_same_v<decltype((i)),int&>);
}
void bind_t(){
auto [i,j] = t;
static_assert(std::is_same_v<decltype((i)),int&>);
}
void bind_ar(){
auto [i,j] = ar;
static_assert(std::is_same_v<decltype((i)),int&>); //For GCC
static_assert(std::is_same_v<decltype((i)),const int&>); //For Clang (and standard?)
}
Run Code Online (Sandbox Code Playgroud)
甲结构化结合到一个副本const的c-阵列被声明常量由锵和非const由GCC.
GCC对c阵列的行为与聚合或类似元组类型的行为一致.
另一方面,从我对标准的阅读中,我认为Clang遵循所写的内容.在[dcl.struct.bind]/1中, e具有类型cv A,其中A是初始化表达式的类型,cv是结构化绑定声明的cv限定符.并且初始化表达式的类型 …
根据这个答案,从C++ 17开始,即使指针具有正确的地址,并且正确的类型解除引用它也会导致未定义的行为.
alignas(int) unsigned char buffer[2*sizeof(int)];
auto p1=new(buffer) int{};
auto p2=new(p1+1) int{};
*(p1+1)=10; // UB since c++17
Run Code Online (Sandbox Code Playgroud)
原因是指针值p1+1是一个指针过去的对象.可以使用以下示例将此示例恢复为已定义的行为std::launder:
*std::launder(p1+1)=10; // still UB?
Run Code Online (Sandbox Code Playgroud)
其次,在下列情况下它是否也有用?
alignas(int) unsigned char buffer[3*sizeof(int)];
auto pi = new (buffer) int{};
auto pc = reinterpret_cast<unsigned char*>(pi);//not a "pointer to" an element of buffer
//since buffer[0] and *pc
//are not pointer interconvertible
//pc+2*sizeof(int) would be UB
auto pc_valid = std::launder(pc) //pc_valid is a pointer to an element of buffer …Run Code Online (Sandbox Code Playgroud) 概念未实例化([temp.spec]).[注意:表示概念特化的id表达式被计算为表达式([expr.prim.id]).[...]
这是否意味着此规则([temp.point]/8)不适用?
如果两个不同的实例化点根据单定义规则给出模板特化的不同含义,则程序形成错误,不需要诊断.
例如,如果此规则不适用,则以下代码形式良好:
template<class T>
concept Complete = sizeof(T)==sizeof(T);
struct A;
constexpr inline bool b1 = Complete<A>; //Complete<A>==false;
struct A{};
constexpr inline bool b2 = Complete<A>; //Complete<A>==true;
Run Code Online (Sandbox Code Playgroud)
这个问题是后面这一个
由于构造函数中的B基类子项目的复制构造,以下代码无法使用Gcc和Clang进行编译A:
struct B{
B();
B(const B&) =delete;
};
struct A:B{
A():B(B()){} //=> error: use of deleted function...
};
Run Code Online (Sandbox Code Playgroud)
不过根据[class.base.init]/7:
的表达式列表或支撑-INIT列表在MEM-初始化是根据[dcl.init]用于初始化规则用来初始化指定子对象(或者,在一个委派构造,完整的类对象的情况下)直接初始化.
因此初始化规则对于成员或直接基础是相同的.对于成员子对象,Gcc和Clang不使用已删除的复制构造函数:
struct A2:B{
B b;
A2():b(B()){} //=> OK the result object of B() is b
};
Run Code Online (Sandbox Code Playgroud)
这不是Clang和Gcc的编译器错误吗?是不是B应该省略复制构造函数A()?
有趣的是,即使gcc检查复制结构是否格式正确,它也会忽略此复制构造函数调用,请参阅此处的程序集
在 C++20 标准中,说数组类型是隐式生命周期类型。
这是否意味着可以隐式创建非隐式生命周期类型的数组?这种数组的隐式创建不会导致数组元素的创建?
考虑这个案例:
//implicit creation of an array of std::string
//but not the std::string elements:
void * ptr = operator new(sizeof (std::string) * 10);
//use launder to get a "pointer to object" (which object?)
std::string * sptr = std::launder(static_cast<std::string*>(ptr));
//pointer arithmetic on not created array elements well defined?
new (sptr+1) std::string("second element");
Run Code Online (Sandbox Code Playgroud)
从 C++20 开始,这段代码不再是 UB 了吗?
也许这种方式更好?
//implicit creation of an array of std::string
//but not the std::string elements:
void * ptr = operator new(sizeof …Run Code Online (Sandbox Code Playgroud) 我们来考虑这个示例代码:
struct sso
{
union {
struct {
char* ptr;
char size_r[8];
} large_str;
char short_str[16];
};
const char* get_tag_ptr() const {
return short_str+15;
}
};
Run Code Online (Sandbox Code Playgroud)
在[basic.expr]中指定只要结果指向数组的另一个元素(或超过对象的末尾或最后一个元素),就允许指针运算.尽管如此,如果数组是联合的非活动成员,则在本节中未指定会发生什么.我相信这不是问题short_str+15,永远不是UB.这样对吗?
以下问题清楚地表明了我的意图
在c ++标准中,在[basic.lval] /11.6中说:
如果程序试图通过以下类型之一以外的glvalue访问对象的存储值,则行为未定义:[...]
- 聚合或联合类型,包括其元素或非静态数据成员中的上述类型之一(包括递归地,子聚合或包含联合的元素或非静态数据成员),[...]
这句话是严格别名规则的一部分.
它是否允许我们通过类成员访问进行别名?
class A{ //"casted to" aggregate
int i;
float g;
};
int j=10;
int l = reinterpret_cast<A*>(&j)->i;
Run Code Online (Sandbox Code Playgroud)
根据标准,只有在对象受到左值到右值转换 [conv.lval]/2的情况下才能访问对象值.
[expr.ref]没有规定object-expression必须引用该类型的对象,只是glvalue必须具有类类型(object-expression是点"."左侧的表达式).然而,有一句话反复出现在与成员访问相关的句子中,这可能意味着对我忽略的程序的限制.例如[expr.ref] /4.1:
如果E2是静态数据成员且E2的类型是T,则E1.E2是左值; 表达式指定类的命名成员.
"指定"是否意味着该成员在其生命周期内并且我不能使用类成员访问来进行伪造别名?或者[basic.lval] /11.6允许它?
在[dcl.init] /17.6中,明确写出,对于括号初始化的情况,会发生复制省略:
如果初始化表达式是prvalue且源类型的cv-nonqualified版本与目标类相同,则初始化表达式用于初始化目标对象.[ 例子: T x = T(T(T())); 调用T默认构造函数来初始化x.- 结束例子 ]
但是在列表初始化的情况下,上面的段落不适用,我没有发现任何类似的东西.见[dcl.init.list].
那么为什么在这种情况下有复制省略:T x{T(T())};根据C++ 17标准.