关于枚举,C++ 14(正是N4296)在7.2:11中说:
每个枚举名称和每个未作用域的枚举器都在包含枚举说明符的作用域中声明.
现在如果命名空间N包含枚举E的opaque-enum声明会发生什么,稍后枚举是从全局命名空间完全声明的?我们应该在全局命名空间或命名空间N中找到它的枚举器吗?
当然,为了不透明地声明一个无范围的枚举,它应该有一个固定的底层类型.考虑以下代码.
namespace N { enum E : int; }
enum N::E : int {A,B};
namespace N {
int foo() {
return int(::N::B);
}
}
int bar() {
//return int(::A);
return int(A);
}
Run Code Online (Sandbox Code Playgroud)
第一行bar被注释掉,因为clang++ -std=c++14说:
全局命名空间中没有名为"A"的成员; 你的意思是'A'吗?
Gcc无法在bar()中编译这两行.所以gcc和clang都声明了名称空间中的枚举N.
所以我的问题是:
A,B在全局命名空间中定义?bar函数中,为什么::A不引用枚举器,但简单A呢?::N::B函数中的表达式N::foo表示枚举器?编辑1:原始声明是enum ::N::E : int {A,B};,但gcc无法解析它(错误报告),所以我删除了使用的主要冒号enum N::E : int {A,B}; …
[conv.qual] / 1中的示例说该类型const int **具有两个cv分解。
类型T的cv分解是cv_i和P_i的序列,使得T为
“ cv_0 P_0 cv_1 P_1?cv_ {n?1} P_ {n?1} cv_n U”
对于n?0,其中每个cv_i是一组cv限定词([basic.type.qualifier]),每个P_i是“指向”的指针([dcl.ptr]),“指向类型为Ci的类的成员的指针”([dcl.mptr]),“ N_i数组”或“的未知范围的数组”([dcl.array])。如果P_i指定一个数组,则元素类型上的cv限定符cv_ {i + 1}也被视为该数组的cv限定符cv_i。[?示例:由type-id表示的类型
const int **具有两个cv分解,以U为“int”和“指向的指针const int”。结束示例]在T的最长cv分解中,第一个cv限定词之后的n元组,即cv_1,cv_2,…,cv_n,被称为T的cv限定签名。
为什么类型没有三个分解,第三个分解为n = 0,cv_0为空,U =“指向”的指针const int?
从C ++ 17开始(更确切地说,从p0135r1开始),数组到指针的转换涉及临时实现-conv.array:
可以将“ NT数组”类型或“ T的未知边界数组”类型的左值或右值转换为“指向T的指针”类型的prvalue。 应用临时物化转换([conv.rval])。 结果是一个指向数组第一个元素的指针。
为什么?临时物化仅适用于prvalues- conv.rval:
可以将类型T的prvalue转换为类型T的xvalue。此转换通过将临时对象作为其结果对象来评估prvalue,从而从prvalue初始化类型T的临时对象([class.temporary])。表示临时对象的xvalue。T应为完整类型。
那么,在数组到指针转换的情况下,将临时实现应用于什么?到结果指针prvalue?
在以下示例中,是否会发生临时实现?
void foo(int *a) {}
int main() {
int arr[4];
foo(arr);
}
Run Code Online (Sandbox Code Playgroud) 在C++中,特别是在C++ 14 n4296中,有两个段落讨论了枚举器的类型,这似乎与我相矛盾.见7.2/5(10.2/5 in n4659):
每个枚举定义一个与所有其他类型不同的类型.每个枚举也有一个基础类型.可以使用enum-base显式指定基础类型.对于作用域枚举类型,如果未明确指定,则基础类型为int.在这两种情况下,基础类型都被认为是固定的.在枚举说明符的右括号之后,每个枚举器都有其枚举类型.如果基础类型是固定的,那么闭括号之前的每个枚举器的类型是基础类型,枚举器定义中的constant-expression应该是基础类型的转换常量表达式[...]
5.1.1/11(8.1.4.2/4 in n4659)写道:
表示枚举(7.2)的嵌套名称说明符,后跟该枚举的枚举数名称,是引用枚举数的qualified-id.结果是枚举器.结果的类型是枚举的类型.结果是一个prvalue.
然后,当我们在关闭声明的括号之前通过嵌套名称说明符引用枚举器时会发生什么?以下面的代码为例:
template < typename T1, typename T2 >
struct fail_if_not_same {
static_assert(std::is_same<T1, T2>::value, "Fail!");
static constexpr int value = 0;
};
enum class E : short {
A,
B = A + 1,
C = fail_if_not_same<decltype(B), short>::value,
D = fail_if_not_same<decltype(E::B), short>::value
};
Run Code Online (Sandbox Code Playgroud)
E::B上面的表达式是什么类型的?这是标准的矛盾吗?gcc和clang都遵循7.2/5.
我经常假设枚举的大小与其基础类型的大小相同.但它是否受标准规定?
标准(C++ 14,n4296)表示每个枚举都有一个基础类型(7.2/5).该标准还说对象表示为字节序列,并且对象的大小与其表示相关:
3.9/4类型为T的对象的对象表示是由类型为T的对象占用的N个无符号字符对象的序列,其中N等于sizeof(T).
5.3.3/1 sizeof运算符产生其操作数的对象表示中的字节数.
但是,我无法在枚举的基础类型和对象表示之间找到任何关系.有没有?如果没有,那么我认为枚举的sizeof不必是其基础类型的sizeof.
所以我的问题是:
枚举的基础类型与其对象表示之间是否存在任何关系?
sizeof(std::underlying_type_t<E>) == sizeof(E)对于任何枚举E ,标准是否真的需要?
C ++ 14标准(N4296)在8.5 / 17.6.1中说
如果初始化是直接初始化,则考虑构造函数。列举了适用的构造函数,并通过重载解析选择了最佳的构造函数。[...]如果没有构造函数适用,或者重载解决方案不明确,则初始化格式错误。
因此,在直接初始化中,仅考虑构造函数-忽略转换函数。在以下代码中,没有适用的构造函数A,只有的转换函数B。但是,代码为什么会编译?
struct A{};
struct B{
operator A(){ return A{}; }
};
int main() {
B b;
A a(b); // direct-initialization
}
Run Code Online (Sandbox Code Playgroud) c++ constructor language-lawyer overload-resolution implicit-conversion
第 3.2.3 节中的x86_64 System V ABI指定函数调用的哪些参数进入哪些寄存器以及哪些被压入堆栈。我很难理解聚合分类的算法,它说(突出显示的是我的):
\n\n聚合(结构体和数组)和联合类型的分类工作原理如下:
\n\n
\n …- 如果对象的大小大于八个字节,或者包含未对齐的字段,则它具有 MEMORY 类。
\n- 如果 C++ 对象对于调用而言非常重要(如 C++ ABI13 中所指定),则它通过不可见引用传递(该对象在参数列表中被具有类 INTEGER 的指针替换)。
\n- 如果聚合的大小超过一个八字节,则每个字节将被单独分类。每个八字节都被初始化为 NO_CLASS 类。
\n- 对象的每个字段都被递归分类,以便始终考虑 两个字段。由此产生的类是根据八字节中字段的类来计算的: (a) 如果两个类相等,则这就是结果类。(b) 如果其中一个类是 NO_CLASS,则结果类是另一个类。(c) 如果其中一个类是 MEMORY,则结果是 MEMORY 类。(d) 如果其中一个类是 INTEGER,则结果是 MEMORY 类。是整数。(e) 如果类之一是 X87、X87UP、COMPLEX_X87 类,则使用 MEMORY 作为类。 (f) 否则使用类 SSE。
\n- 然后进行合并后清理: (a) 如果其中一个类是 MEMORY,则整个参数将在内存中传递。(b) 如果 X87UP 前面没有 X87,则整个参数将在内存中传递。(c) 如果聚合的大小超过两个八字节,并且第一个八字节是\xe2\x80\x99t SSE 或任何其他八字节是\xe2\x80\x99t SSEUP,则整个参数将在内存中传递。(d) 如果 SSEUP 前面没有 SSE 或 SSEUP,则转换为 SSE
\n
考虑以下程序:
struct A {
A(int){}
A(A const &){}
};
int main() {
A y(5);
}
Run Code Online (Sandbox Code Playgroud)
变量y使用表达式直接初始化5。重载分辨率选择构造函数A::A(int),这是我期望和想要的,但是为什么会发生呢?
可能是因为两个原因:
要么过载A::A(int)才是更好的匹配A::A(A const &),否则第二个根本不是可行的过载。
问题:在以上程序中,构造函数是否A::A(A const &)是可行的重载来初始化y?
在以下程序中,应选择哪个(如果有)转换功能,为什么?
int r;
struct B {};
struct D : B {};
struct S {
D d;
operator D&(){r=1; return d;} // #1
operator B&(){r=2; return d;} // #2
};
int main() {
S s;
B& b = s;
return r;
}
Run Code Online (Sandbox Code Playgroud)
gcc和clang选择均选择转换功能#2。但为什么?
标准说:
(1)在[dcl.init.ref]中指定的条件下,可以将引用直接绑定到将转换函数应用于初始化程序表达式的结果。重载解析用于选择要调用的转换函数。假设“对cv1 T的引用”是要初始化的引用的类型,而“ cv S”是初始化程序表达式的类型,其中S为类类型,则按以下方式选择候选函数:
(1.1)-考虑S的转换函数及其基类。那些未隐藏在S中且产生类型“对cv2 T2的左值引用”(初始化对函数的左值引用或右值引用时)或“ cv2 T2”或“对cv2 T2的右值引用”的非显式转换函数将右值引用或左值引用初始化为函数),其中“ cv1 T”与“ cv2 T2”的引用兼容)是候选函数。对于直接初始化,未隐藏在S中的那些显式转换函数会产生类型“对cv2 T2的左值引用”(初始化对函数的左值引用或右值引用)或“对cv2 T2的右值引用”(当初始化对函数的右值引用或左值引用),
(2)参数列表有一个参数,它是初始化器表达式。[?注:此参数将与转换函数的隐式对象参数进行比较。-?尾注?]
这里我们有两个候选函数#1和#2。两者都可行-如果删除其中之一,程序仍会编译。这两个转换函数仅采用隐式参数,并且具有相同的cv和ref限定。因此,没有一个应该是最可行的,并且该程序也不应编译。为什么编译?
c++ language-lawyer overload-resolution implicit-conversion reference-binding
c++ ×8
c++14 ×3
abi ×1
c ×1
constructor ×1
enumerator ×1
enums ×1
linux ×1
pointers ×1
type-systems ×1
x86-64 ×1