在下一个例子中,U带有私有析构函数的类有一个友元函数foo。这个友元函数的参数类型U为默认值U{}:
class U{ ~U(); friend void foo(U); };
void foo(U = {});
Run Code Online (Sandbox Code Playgroud)
Clang 和 MSVC 接受此代码,但 GCC 拒绝它并显示错误
error: 'U::~U()' is private within this context
2 | void foo(U = {});
| ^
Run Code Online (Sandbox Code Playgroud)
演示:https : //gcc.godbolt.org/z/eGxYGdzj3
哪个编译器就在这里,友谊是否扩展到 C++ 中的默认参数?
我们知道,下面的代码格式错误,因为该成员x位于依赖基类中.但是,更改x为this->x指示的行将修复错误.
template <typename T>
struct B {
int x;
};
template <typename T>
struct C : B<T> {
void f() {
int y = x; // Error!
}
};
int main() {
C<int> c;
c.f();
}
Run Code Online (Sandbox Code Playgroud)
我想解释一下标准中如何指定这种行为.根据[temp.dep]/3:
在类或类模板的定义中,如果基类依赖于模板参数,则在类模板或成员的定义点或在实例化实例化期间,不会在非限定名称查找期间检查基类作用域.类模板或成员.
这似乎解释了为什么x单独使用失败.x在定义点查找名称,不检查基类范围.但是,如果我们使用this->x怎么办?现在名称x是依赖的,其查找被推迟到实例化.但引用的段落似乎暗示即使在实例化时x也不应该找到,因为查找xin 仍然this->x是不合格的查找.
显然,实现不会以这种方式运行,并且人们普遍认为,一旦模板被实例化,就会搜索基类范围.
从/sf/answers/1863048911/可以看出,标准保证以下标题#include <initializer_list>:
<utility><string><algorithm><random><valarray><regex>这些头文件中的大多数都声明了至少一个带std::initializer_list<E>参数的函数,所以它是有意义的.然而,
<array>,<stack>并且<queue>没有这样的功能,尽管在这里统一处理所有容器也许是有意义的.<utility> 没有这样的功能.<iterator>确实有一个带initializer_list参数(rbegin,rend)的函数,但它没有指定包含<initializer_list>.这些例外背后的理由是什么?
请考虑以下代码,由同事提供:
#include <array>
#include <string>
int main() {
const int size = 4;
return [size]() {
std::array<std::string, size> a; // *
return a.size();
}();
}
Run Code Online (Sandbox Code Playgroud)
它已被Clang 5.0.0接受但被GCC 7.2拒绝,并且主题行的错误消息为:
error: '__closure' is not a constant expression
Run Code Online (Sandbox Code Playgroud)
哪个编译器是对的?
该std::launder函数要求通过结果可以访问的每个字节都可以通过参数访问。“可达到”的定义如下:
通过一个指向对象Y的指针值可以到达一个存储字节,如果该对象位于Y所占的存储空间之内,则该对象可以与Y进行指针可互转换;如果Y是一个数组元素,则该数组将立即包含在数组对象中。
根据对另一个问题的回答,此限制“ ...意味着您将无法launder获得一个指针,该指针将允许您访问比源指针值允许更多的字节,这将带来不确定的行为。”
这对于TC给出的示例是有意义的,但是在原始对象已被新对象替换的情况下,我不理解如何解释它,这是预期的原始目的std::launder。该标准具有以下示例:
struct X { const int n; };
X *p = new X{3};
const int a = p->n;
new (p) X{5}; // p does not point to new object (6.8) because X::n is const
const int b = p->n; // undefined behavior
const int c = std::launder(p)->n; // OK
Run Code Online (Sandbox Code Playgroud)
在这种情况下,到时间std::launder被调用时,由p- 指向的对象--原始X对象--已经不存在,因为在其所占用的存储中创建新对象已隐式结束了其生命周期([基本。生活] /1.4)。因此,似乎有通过任何字节可达p因为 …
好的,我知道这看起来像是复制了为什么在使用之前需要声明函数?但似乎现有答案并未完全解决所有细节问题.
我知道C++最初是在80年代设计的,所以它可以在一次通过中翻译,因为计算机很慢.好.但是最新的标准是在2011年发布的,所以我不明白为什么C++编译器现在不能做需要多次传递的事情.它仍然会伤害性能,是的,但只有在它真的变得必要的时候.所以以下内容仍然只需要一次传递:
void foo();
int main() { foo(); }
void foo() {}
Run Code Online (Sandbox Code Playgroud)
而对于以下情况,编译器可以使两个(并且更慢),因为它不知道foo是函数还是类型,直到它看到下面的声明:
int main() { foo(); }
void foo() {}
Run Code Online (Sandbox Code Playgroud)
如果你试图使用一个函数而不首先声明它,并且声明根本不在当前的翻译单元中,那么这将是一个错误.但是如果它在同一个翻译单元中,那么编译器可以只进行额外的传递.
我的同事辩称,这样的功能可以节省大量的开发人员时间,并且可以避免声明和定义不匹配的问题.而且我确信这已被多次提出并且每次都被拒绝.拒绝它的实际原因是什么,即委员会的理由?
考虑以下示例(Coliru链接):
template <class... T> struct S { using type = int; };
template <class... T>
void f(typename S<T...>::type) {
static_assert(sizeof...(T) == 0);
}
template <class... T>
void g(typename S<T...>::type, S<T...>*) {}
int main() {
f(42);
g(42, nullptr);
}
Run Code Online (Sandbox Code Playgroud)
GCC和Clang都对致电感到满意f,但对的致电并不满意g。
在对的调用中f,尽管T...出现在非推导上下文中,但最终被推导为空。这似乎是由于[temp.arg.explicit] / 4造成的:
...否则未推断出的尾随模板参数包([temp.variadic])将被推断为模板参数的空序列。...
g但是,在对的调用中,在推断的上下文中T...另外出现的事实(这导致尝试尝试并失败)似乎导致g变得不可行。T...一旦尝试并失败,似乎没有退缩为空的迹象。
Is the following program well-formed?
#include <vector>
struct A {
explicit A(int) {}
};
int main() {
std::vector<int> vi = {1, 2, 3, 4, 5};
std::vector<A> va(vi.begin(), vi.end());
}
Run Code Online (Sandbox Code Playgroud)
According to C++17 [sequence.reqmts], the requirement for
X u(i, j);
Run Code Online (Sandbox Code Playgroud)
where X is a sequence container, is:
Tshall beEmplaceConstructibleintoXfrom*i.
However, in the preceding paragraph it is stated that:
iandjdenote iterators satisfying input iterator requirements and refer to elements implicitly convertible to …
该标准区分了抛出异常时发生的两种破坏形式.强调我的.
§15.2/ 1
当控制从throw-expression传递到处理程序时,将为输入try块后构造的所有自动对象调用析构函数.自动对象按照完成构造的相反顺序销毁.
§15.2/ 2
任何存储持续时间的对象,其初始化或销毁由异常终止,将为其所有完全构造的子对象(不包括类似联合的类的变体成员)执行析构函数,即对于其主要构造函数的子对象( 12.6.2)已完成执行并且析构函数尚未开始执行.类似地,如果对象的非委托构造函数已完成执行并且该对象的委托构造函数以异常退出,则将调用该对象的析构函数.如果对象是在new-expression中分配的,则调用匹配的释放函数(3.7.4.2,5.3.4,12.5)(如果有)以释放对象占用的存储空间.
§15.2/ 3
为从try块到throw-expression的路径构造的自动对象调用析构函数的过程 称为" 堆栈展开"."如果在堆栈展开期间调用的析构函数以异常退出,
std::terminate则调用(15.5.1).[ 注意:因此析构函数通常应该捕获异常,而不是让它们从析构函数中传播出来.- 结束说明 ]
因此,似乎我们有(a)堆栈展开,它会破坏自动对象,以及(b)破坏构造函数或析构函数通过异常退出的对象的完全构造的子对象,无论存储持续时间如何都会发生.
仔细阅读§15.2/ 1表明,只有当控制传递给处理程序时,才会发生堆栈展开,如果未处理异常,则可能会发生堆栈展开的可能性.的确,§15.5.2/ 2说,
在没有找到匹配处理程序的情况下,无论堆栈是否在
std::terminate()被调用之前被展开,它都是实现定义的."
但是§15.2/ 2的措辞似乎没有留下这种可能性.它只是说初始化或破坏必须由异常终止 - 而不是控件必须传递给处理程序.所以我的解释是,即使没有处理异常,子对象仍然被销毁.这是正确的解释吗?
例如,假设我们有
std::vector<int> V;
ComplicatedObject* p = new ComplicatedObject();
Run Code Online (Sandbox Code Playgroud)
并且它ComplicatedObject的构造函数抛出,并且不处理异常.然后是否V被销毁是实现定义的.它是否也是实现定义的,是否*p销毁了完全构建的子对象?请注意,此类对象没有自动存储持续时间.
考虑以下:
struct X {
X() {}
X(X&&) { puts("move"); }
};
X x = X();
Run Code Online (Sandbox Code Playgroud)
在C++ 14中,尽管移动构造函数由于[class.copy]/31而具有副作用,但移动可能会被省略,
在下列情况下允许复制/移动操作的省略...当未绑定到引用(12.2)的临时类对象被复制/移动到具有相同cv-nonqualified的类对象时类型
在C++ 17中,这个子弹被删除了.相反,由于[dcl.init] /17.6.1,保证可以省略此举:
如果初始化表达式是prvalue且源类型的cv-nonqualified版本与目标类相同,则初始化表达式用于初始化目标对象.[ 示例:
T x = T(T(T()));调用T默认构造函数进行初始化x.- 结束例子 ]
到目前为止,我所陈述的事实众所周知.但现在让我们更改代码,使其显示为:
X x({});
Run Code Online (Sandbox Code Playgroud)
在C++ 14中,执行重载解析并使用默认构造函数将其{}转换为临时类型X,然后移入x.复制省略规则允许省略此移动.
在C++ 17中,重载决策是相同的,但现在[dcl.init] /17.6.1不适用,并且C++ 14中的子弹不再存在.没有初始化表达式,因为初始化程序是braced-init-list.相反,似乎[dcl.init] /(17.6.2)适用:
否则,如果初始化是直接初始化,或者如果它是复制初始化,其中源类型的cv-nonqualified版本与目标类相同的类或派生类,则考虑构造函数.列举了适用的构造函数(16.3.1.3),并通过重载解析(16.3)选择最佳构造函数.调用所选的构造函数来初始化对象,初始化表达式或表达式列表作为其参数.如果没有构造函数适用,或者重载决策是不明确的,则初始化是错误的.
这似乎需要调用移动构造函数,如果标准中的其他地方有一条规则说可以忽略它,我不知道它在哪里.
c++ ×10
c++17 ×3
templates ×3
c++11 ×1
copy-elision ×1
declaration ×1
friend ×1
lambda ×1
name-lookup ×1
stdlaunder ×1