根据§7.2/ 5和§7.2/ 6,下面的代码不应该打印1 1而不是4 4?
#include <iostream>
enum A { a = (char)1, b, c }; // underlying type is not fixed
int main() {
std::cout << sizeof(a) << ' ' << sizeof(A) << '\n';
}
Run Code Online (Sandbox Code Playgroud)
编辑
从§7.2/ 5开始:
如果基础类型未修复,则每个枚举器的类型是其初始化值的类型:
- 如果为枚举数指定了初始值设定项,则初始化值与表达式具有相同的类型,而常量表达式应为整数常量表达式(5.19).
clang,gcc和VS2013都抱怨重新定义win main(),但是我在标准中找不到任何禁止它的东西.
namespace N {
extern int j;
int j;
}
int main()
{
extern int w;
int w;
}
Run Code Online (Sandbox Code Playgroud)
这些段落说明了extern在块作用域中使用声明,但它们似乎不能证明错误消息是正确的:
§3.3.1/ 4
给定一个声明区域中的一组声明,......
[注意:这些限制适用于引入名称的声明性区域,该名称不一定与声明发生的区域相同.特别是,详细类型说明符(7.1.6.3)和友元声明(11.3)可能会将一个(可能不可见)名称引入封闭的名称空间中; 这些限制适用于该地区.本地extern声明(3.5)可能会在声明出现的声明区域中引入一个名称,并在一个封闭的命名空间中引入一个(可能不可见)名称 ; 这些限制适用于这两个地区. - 尾注]
§3.3.2/ 10
[注意:Friend声明引用的函数或类是最近的封闭命名空间的成员,但它们不会在该命名空间中引入新名称(7.3.1.2).块作用域中的函数声明和使用块作用域中的extern说明符的变量声明引用作为封闭命名空间成员的声明,但它们不会在该作用域中引入新名称. - 尾注]
显然,从§3.3.1/ 4开始,这个代码片段无法编译,因为它包含两个A在全局命名空间中具有相同名称的不同实体,extern int A;并且static int A = 101;.也就是说,一个有外部,另一个有内部联系.
#include <iostream>
extern int A;
static int A = 101;
class A{};
int main()
{
std::cout << A << '\n';
}
Run Code Online (Sandbox Code Playgroud)
那么为什么这个代码会编译?
#include <iostream>
static int A = 101;
extern int A;
class A{};
int main()
{
std::cout << A << '\n';
}
Run Code Online (Sandbox Code Playgroud)
编辑
我认为这个被认为是重复的问题的接受答案基本上说,在第二个片段中,变量A仍然有内部联系,尽管extern声明.但这与我在下面对@dyp的评论中提到的第3.5/4段不一致.
§3.5/ 4:
未命名的命名空间或在未命名的命名空间中直接或间接声明的命名空间具有内部链接.所有其他名称空间都有外部链接.具有名称空间作用域的名称上面没有给出内部链接,如果是名称,则具有与封闭名称空间相同的链接
- 一个变量; 要么
...
编辑1:
OP使用§3.5/ 6来证明他对另一个问题的回答.
§3.5/ 6(强调我的):
在块作用域中声明的函数的名称和由块作用域 extern声明声明的变量的名称具有链接.如果存在具有相同名称和类型的链接的实体的可见声明,忽略在最内部封闭命名空间范围之外 …
这个代码在编译Coliru与警告[未初始化成员a[1].i和a[2].i在std::cout <<在表达式main()]但在通常编译Ideone.
#include <iostream>
struct A
{
int i;
A(int j) : i{j} {};
A() = default;
};
int main() {
A a[3] = { A(1) };
std::cout << a[1].i << ' ' << a[2].i << '\n';
}
Run Code Online (Sandbox Code Playgroud)
根据我对iso§8.5p7的解释,Ideone是正确的,因为本节中的第4个要点.
这是N3797的§8.5p7
对值类型T的对象进行值初始化意味着:
- 如果T是一个(可能是cv-quali fi ed)类类型(第9条),没有默认构造函数(12.1)或者是用户提供或删除的默认构造函数,那么该对象是默认初始化的;
- 如果T是一个(可能是cv-quali fi ed)类类型而没有用户提供或删除的默认构造函数,那么该对象是零初始化的,并且检查默认初始化的语义约束,如果T有一个非平凡的默认构造函数,该对象是默认初始化的;
- 如果T是数组类型,那么每个元素都是值初始化的;
- 否则,该对象被零初始化.
值初始化的对象被视为构造,因此受本国际标准的规定适用于"构造"对象,"构造函数已完成的对象"等,即使没有为该对象调用构造函数也是如此.初始化.
C++ 11标准中的§7.3.1.2/ 3(重点是我的):
首先在名称空间中声明的每个名称都是该名称空间的成员.如果非本地类中的友元声明首先声明一个类或函数,那么友元类或函数是最内层封闭命名空间的成员.在非命名查找(3.4.1)或限定查找(3.4.3)之前找不到朋友的名称,直到在该命名空间范围内提供匹配声明(在授予友谊的类定义之前或之后).如果调用了友元函数,则可以通过名称查找找到其名称,该名称查找考虑名称空间中的函数和与函数参数类型相关联的类(3.4.2).如果友元声明中的名称既不是限定语句也不是模板标识,并且声明是函数或详细类型说明符,则确定实体是否先前已声明的查找不应考虑最内层封闭命名空间之外的任何范围.[注意:其他形式的友元声明不能声明最内层封闭命名空间的新成员,因此遵循通常的查找规则.
例:
Run Code Online (Sandbox Code Playgroud)// Assume f and g have not yet been defined. void h(int); template <class T> void f2(T); namespace A { class X { friend void f(X); // A::f(X) is a friend class Y { friend void g(); // A::g is a friend friend void h(int); // A::h is a friend // ::h not considered friend void f2<>(int); // ::f2<>(int) is a friend }; }; // A::f, A::g and A::h are not visible …
我试图理解作者3.3.4 Suppressing Operations在他的新书(TCPL第4版)中的建议,但无济于事.
从书中摘录
对层次结构中的类使用默认副本或移动通常是一个灾难:只给出指向基类的指针,我们根本不知道派生类有哪些成员(§3.3.3),所以我们无法知道如何复制它们.所以,最好的办法是删除默认副本和移动操作; 也就是说,要消除这两个操作的默认定义:
class Shape {
public:
Shape(const Shape&) =delete; // no copy operations
Shape& operator=(const Shape&) =delete;
Shape(Shape&&) =delete; //no move operations
Shape& operator=(Shape&&) =delete;
~Shape();
};
Run Code Online (Sandbox Code Playgroud)
现在,编译器将捕获复制Shape的尝试.如果需要复制类层次结构中的对象,请编写某种克隆函数(第22.2.4节).
例如,下面的代码不能编译Shape(const Shape&) = delete;,因为clone()函数调用了Shape复制构造函数.
#include <iostream>
class Shape
{
public:
virtual ~Shape() {}
Shape() {}
Shape(const Shape&) {};
virtual Shape* clone() const = 0;
};
class Circle: public Shape
{
public:
Circle(int i) : a(i) {}
Circle* clone() const { …Run Code Online (Sandbox Code Playgroud) 有人可以指出标准中的哪个子句支持在Coliru中获得的以下行为,对于该片段:
#include <iostream>
class A
{
int i;
float x;
public:
A() : i(10) {}
A(int i) : i(i) {}
int GetI() { return i; }
float GetF() { return x; }
};
int main()
{
A a;
A b(1);
A x{};
A y{1};
std::cout << a.GetI() << '\n';
std::cout << a.GetF() << '\n';
std::cout << b.GetI() << '\n';
std::cout << b.GetF() << '\n';
std::cout << x.GetI() << '\n';
std::cout << x.GetF() << '\n';
std::cout << y.GetI() << '\n';
std::cout …Run Code Online (Sandbox Code Playgroud) 从C++ 11标准§8.5p6我们得到:
如果程序要求对const限定类型T的对象进行默认初始化,则T应为具有用户提供的默认构造函数的类类型.
下面的代码应该不会编译.但它确实在Coliru和Ideone都有.
class A{};
int main() {
const A a;
}
Run Code Online (Sandbox Code Playgroud)
编辑:
在尝试理解这里发生了什么时,我最终得到了以下代码,它编译(至少它符合标准,与A用户提供的构造函数一样).但后来出现了以下问题:哪个标准子句确保a.b.j用0初始化(参见Ideone中的代码),下面是什么?
#include <iostream>
struct B { int j; B(){ std::cout << "B()" << '\n'; } };
struct A
{
struct B b;
int i;
public:
A(): i(1) { std::cout << "A()" << '\n'; }
};
int main() {
const A a;
std::cout << a.b.j << '\n';
std::cout << a.i << '\n';
}
Run Code Online (Sandbox Code Playgroud)
EDIT1:
很抱歉上面的编辑,但我还没有使用Unix.上周, …
对于聚合
struct S{int i, j;};
Run Code Online (Sandbox Code Playgroud)
根据N3797§8.5p16 声明S s({1, 2});和S s({1});执行直接初始化:
表单中发生的初始化
Run Code Online (Sandbox Code Playgroud)T x(a); T x{a};在
new表达式(5.3.4)中,static_cast表达式(5.2.9),函数表示法类型转换(5.2.3)以及基本和成员初始化器(12.6.2)称为直接初始化.
但§8.5p17似乎没有表征它们:
初始化器的语义如下.的目标类型是对象或引用的类型被初始化和源类型是初始化表达式的类型.如果初始化程序不是单个(可能带括号的)表达式,则不定义源类型.
如果初始化程序是(非括号的)braced-init-list,则对象或引用是列表初始化的(8.5.4).
如果目标类型是引用类型,请参见8.5.3.
如果目标类型是字符数组,数组
char16_t,数组char32_t或数组wchar_t,并且初始值设定项是字符串文字,请参见8.5.2.如果初始化程序是
(),则对象进行值初始化.否则,如果目标类型是数组,则程序格式错误.
如果目标类型是(可能是cv限定的)类类型:
如果初始化是直接初始化,或者它是复制初始化,其中源类型的cv-nonqualified版本与目标类相同的类或派生类,则考虑构造函数.列举了适用的构造函数(13.3.1.3),并通过重载解析(13.3)选择最佳构造函数.调用所选的构造函数来初始化对象,初始化表达式或表达式列表作为其参数.如果没有构造函数适用,或者重载决策是不明确的,则初始化是错误的.
否则(即,对于剩余的复制初始化情况),可以如13.3中所述枚举可以从源类型转换为目的地类型或(当使用转换函数时)到其派生类的用户定义的转换序列. 1.4,通过重载决策(13.3)选择最好的一个.如果转换不能完成或不明确,则初始化是错误的.选择的函数以初始化表达式作为参数调用; 如果函数是构造函数,则调用初始化目标类型的cv-nonqualified版本的临时函数.临时是一个prvalue.然后,根据上面的规则,调用的结果(对于构造函数的情况是临时的)用于直接初始化作为复制初始化目标的对象.在某些情况下,允许实现通过将中间结果直接构造到正在初始化的对象中来消除此直接初始化中固有的复制; 见12.2,12.8.
否则,如果源类型是(可能是cv限定的)类类型,则考虑转换函数.列举了适用的转换函数(13.3.1.5),并通过重载决策(13.3)选择最佳函数.调用如此选择的用户定义转换以将初始化表达式转换为正在初始化的对象.如果转换不能完成或不明确,则初始化是错误的.
否则,正在初始化的对象的初始值是初始化表达式的(可能已转换)值.如有必要,将使用标准转换(第4节)将初始化表达式转换为目标类型的cv非限定版本; 不考虑用户定义的转换.如果无法进行转换,则初始化不正确.[ 注:类型"的表达式CV1
T"可初始化类型"的对象CV2T"独立于cv修饰符的CV1和CV2.Run Code Online (Sandbox Code Playgroud)int a; const int b = a; int …
该表达式可在标准中的§8.5.4/ 7中的示例中找到(N3797)
unsigned int ui1 = {-1}; // error: narrows
Run Code Online (Sandbox Code Playgroud)
鉴于§8.5.4/ 7及其第4个要点:
缩小转换是隐式转换:
- 从整数类型或未范围的枚举类型到不能表示原始类型的所有值的整数类型,除非源是一个常量表达式,其整数提升后的值将适合目标类型.
我想说这里没有缩小,因为-1是一个常量表达式,其积分提升后的值适合无符号整数.
另见关于积分促销的 §4.5/ 1 :
如果int可以表示源类型的所有值,则除了bool,char16_t,char32_t或wchar_t之外的整数类型的prvalue(其整数转换等级(4.13)小于int的等级)可以转换为int类型的prvalue ; 否则,源prvalue可以转换为unsigned int类型的prvalue.
从4.13开始,我们得到-1(一个int)的等级等于unsigned int的等级,因此它可以转换为unsigned int.
编辑
不幸的是,Jerry Coffin从这个帖子中删除了他的答案.我相信他是在正确的轨道上,如果我们接受这一事实,即在标准的这一变化之后,§8.5.4/ 7中第4个要点的当前读数是错误的.