如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?
众所周知,std::move不应该将其应用于函数返回值,因为它可以阻止RVO(返回值优化).我感兴趣的是,如果我们确实知道RVO不会发生,我们应该怎么做.
这就是C++ 14标准所说的[12.8/32]
当满足复制/移动操作的省略标准时,但不满足异常声明,并且要复制的对象由左值指定,或者当返回语句中的表达式是(可能带有括号的)id-时表达式,用于在最内层封闭函数或lambda-expression的body或parameter-declaration-clause中声明的具有自动存储持续时间的对象,首先执行重载决策以选择复制的构造函数,就像对象由rvalue指定一样.如果第一个重载决策失败或未执行,或者所选构造函数的第一个参数的类型不是对象类型的rvalue引用(可能是cv-qualified),则再次执行重载决策,将对象视为左值.[注意:无论是否发生复制省略,都必须执行此两阶段重载决策.如果未执行elision,它将确定要调用的构造函数,并且即使调用被省略,也必须可以访问所选的构造函数. - 结束说明]
这是本书的解释 Effective Modern C++
标准祝福RVO的部分接着说,如果满足RVO的条件,但编译器选择不执行复制省略,则返回的对象必须被视为右值.实际上,标准要求在允许RVO时,发生复制省略或将std :: move隐式应用于返回的本地对象
据我所知,当返回物体最初不能被省略时,它应被视为rvalue.在这些例子中,我们可以看到,当我们传递大于5object的参数时,否则会被复制.std::move当我们知道RVO不会发生时,这是否意味着我们应该明确写出来?
#include <iostream>
#include <string>
struct Test
{
Test() {}
Test(const Test& other)
{
std::cout << "Test(const Test&)" << std::endl;
}
Test(Test&& other)
{
std::cout << "Test(const Test&&)" << std::endl;
}
};
Test foo(int param)
{
Test test1;
Test test2;
return param > 5 ? std::move(test1) : test2;
}
int main()
{
Test res = foo(2);
}
Run Code Online (Sandbox Code Playgroud)
这个程序的输出是Test(const …
c++ return-value move-semantics return-value-optimization copy-elision
在提到这个问题时,该问题确实具有相同的标题,但我在标准中找到了答案.我继续挖掘这个主题,最后找出一个不适用这个答案的示例代码.
让我们考虑一下这段代码:
template<class T> void func(T* buf); //template I
template<size_t N> void func(char (&buf) [N]); //template II
void g(char (&buf)[3])
{
func(buf) //Error: ambiguous function call (Clang, GCC, ICC, MSVC)
}
Run Code Online (Sandbox Code Playgroud)
根据[temp.func.order]和[temp.deduct.partial]中的部分排序规则,如果通过执行这段代码解释了这个规则,模板II应该比模板I更专业:
template <class T> void func1(T* buf) {}
template <std::size_t N> void func2(char (&buf)[N]) {}
struct invented_T{};
constexpr std::size_t invented_N=42;
void is_template_I_more_specialized(invented_T* buf)
{
func2(buf);
//DO NOT COMPILE
// => template I is not more specialized than …Run Code Online (Sandbox Code Playgroud) 在c ++标准[temp.point]中写道:
依赖于模板参数的表达式的实例化上下文是 在同一翻译单元中模板特化的实例化之前声明的具有外部链接的声明集.
然后在[temp.dep.candidate]中:
对于使用关联命名空间([basic.lookup.argdep])查找的部分,仅找到在模板定义上下文或模板实例化上下文中找到的函数声明.
这是否意味着以下代码应该失败:
namespace A{
struct S{};
}
template<class T>
void g(T a){
f(a); //f will be found by argument dependent lookup
}
namespace A{
static void f(S); //but f doesn't have external linkage
}
void test(A::S i){
g(i);
}
//point of instantiation of g
//A::f(S) doesn't have external linkage
//=> so it's not in the instantiation context of template g ??
Run Code Online (Sandbox Code Playgroud)
这段代码实际编译,那么这个标准段落意味着什么?
根据标准,带支撑的函数式转换总是会产生一个纯右值,[expr.cast]/2
否则,表达式是指定类型的纯右值,其结果对象是用初始化程序直接初始化的。
当指定类型是引用类型时,这很难解释,因为它可能发生在泛型编程中。在这种情况下,编译器采用了特定的行为:
#include <type_traits>
struct A {
A ()=default;
A (const A&);
};
template <class T, class U>
decltype(auto)
f(U& a) {
static_assert (std::is_same_v <decltype (T{a}), T>);
return T{a};
}
#if defined(__clang__) || defined(_MSC_VER)
void g1(A a){
decltype(auto) v = f<A&>(a); //GCC: error try to bind a prvalue to a non-const lvalue
static_assert (std::is_same_v <decltype(v), A&>);
}
#endif
void g2(A a){
decltype(auto) v = f<const A&>(a); //GCC: call the copy constructor of A
//MSVC and Clang: …Run Code Online (Sandbox Code Playgroud) C++标准指定mutex,atomics或conditinal_variable是标准布局类型.
这个规范有什么好处?用户如何利用此属性?
总的来说,如果知道一个类型是标准布局而不知道其实现的细节,我可以获得什么?
我试图了解重载决议.
首先让我们考虑第一种情况:
struct int1{
int val;
operator int&()&{
return val;
}
operator const int &() const&{
return val;
}
};
void f(int &){} //f#1
void f(const int&){} //f#2
void test1(){
int1 x;
f(x);
//Conversion sequence for f#1:
// - int_wrapper& --> int1::operator int&
// => Ranking: user defined conversion rank
//Converison sequence for f#2:
// - int1& --> int1::operator int & --> const int&
// - int1& --> const int1 & --> int1::operator const int&
// => Ranking: …Run Code Online (Sandbox Code Playgroud) 考虑以下两个类:
class B
{
public:
B() { }
B(const B& b) = delete; //Move ctor not implicitly declared
};
class A
{
public:
A() { }
operator B()
{
return B();
}
};
Run Code Online (Sandbox Code Playgroud)
我可以看到为什么这段代码编译得很好:
A a;
B b = a;
Run Code Online (Sandbox Code Playgroud)
遵循复制初始化的规则,对象"a"被转换为类型B的prvalue,因为在C++ 17中不再需要复制构造函数,所以没有错误:
如果T是类类型,并且其他类型的cv-nonqualified版本不是T或从T派生,或者如果T是非类型类型,但是other的类型是类类型,则是用户定义的转换序列可以检查从其他类型转换为T(或从T派生的类型,如果T是类类型并且转换函数可用),并通过重载决策选择最佳的类型.转换的结果,即prvalue临时(直到C++ 17)prvalue表达式(自C++ 17),如果使用转换构造函数,则用于直接初始化对象.最后一步通常是优化的,转换的结果直接在为目标对象分配的内存中构造,但是即使没有使用,也需要访问适当的构造函数(移动或复制).(直到C++ 17)
但是为什么这个直接列表初始化编译呢?
A a;
B b{ a };
Run Code Online (Sandbox Code Playgroud)
我在列表初始化中找不到任何措辞,说明编译器在这种情况下应该尝试将A转换为B. 只考虑构造函数的重载决策:
如果前一阶段没有产生匹配,则T的所有构造函数都参与对由braced-init-list元素组成的参数集的重载解析,并限制只允许非缩小转换
但是在这种情况下,复制构造函数被删除,所以不应该通过重载决策来选择它吗?
这个问题是一个跟进这一个
概念未实例化([temp.spec]).[注意:表示概念特化的id表达式被计算为表达式([expr.prim.id]).[...]
因此,可能由于可访问性而将名称概念特化的表达式赋予不同的值.
如果是这种情况,我想知道将在哪个上下文中评估表达式:
概念定义的背景;
表达的背景;
表达式的上下文递归地应用于概念定义中出现的概念表达式?
例如,什么可能是A::b2和A::b2_rec?的价值?
template<class T>
concept has_private = requires(){ &T::private_;};
template<class T>
concept has_private_rec = has_private<T>;
class B{
int private_;
friend class A;
};
inline constexpr bool b1 = has_private<B>;//I expects false
inline constexpr bool b1_rec = has_private_rec<B>;//I expects false
class A{
static constexpr bool b2 = has_private<B>; //?
static constexpr bool b2_rec = has_private_rec<B>; //?
};
Run Code Online (Sandbox Code Playgroud)
注意Clang实验概念和gcc概念TS实现产生b1和b1_rec的编译错误,但b2和b2_rec为真;
Let's consider this trivial code:
#include <atomic>
std::atomic<int> a;
void f(){
for(int k=0;k<100;++k)
a.load(std::memory_order_relaxed);
}
Run Code Online (Sandbox Code Playgroud)
MSVC, Clang and GCC all perform 100 loads of a, while it seems obvious it could have been optimized away. I expected the function f to be a nop (See generated code here)
Actually, I expected this code generation for a volatile atomic:
volatile std::atomic<int> va;
void g(){
for(int k=0;k<100;++k)
va.load(std::memory_order_relaxed);
}
Run Code Online (Sandbox Code Playgroud)
Why do compilers not optimize away unnecessary atomic loads?
c++ ×10
atomic ×2
c++17 ×2
templates ×2
c++-concepts ×1
c++20 ×1
casting ×1
compiler-bug ×1
copy-elision ×1
linkage ×1
mutex ×1
name-lookup ×1
overloading ×1
return-value ×1
volatile ×1