小编Pas*_* By的帖子

是什么使i = i ++ + 1; 合法的C++ 17?

在开始大喊未定义的行为之前,这在N4659(C++ 17)中明确列出

  i = i++ + 1;        // the value of i is incremented
Run Code Online (Sandbox Code Playgroud)

然而在N3337(C++ 11)

  i = i++ + 1;        // the behavior is undefined
Run Code Online (Sandbox Code Playgroud)

改变了什么?

从我可以收集,从[N4659 basic.exec]

除非另有说明,否则对单个运算符的操作数和单个表达式的子表达式的评估是不确定的.[...]在运算符结果的值计算之前,对运算符的操作数的值计算进行排序.如果相对于同一存储器位置上的另一个副作用或者使用同一存储器位置中的任何对象的值进行的值计算,对存储器位置的副作用未被排序,并且它们不可能是并发的,则行为是未定义的.

其中定义为[N4659 basic.type]

对于简单的可复制类型,值表示是对象表示中的一组位,用于确定,该是实现定义的值集的一个离散元素

来自[N3337 basic.exec]

除非另有说明,否则对单个运算符的操作数和单个表达式的子表达式的评估是不确定的.[...]在运算符结果的值计算之前,对运算符的操作数的值计算进行排序.如果对标量对象的副作用相对于同一标量对象的另一个副作用或使用相同标量对象的值进行的值计算未被排序,则行为未定义.

同样,值定义在[N3337 basic.type]

对于简单的可复制类型,值表示是对象表示中的一组位,用于确定,该是实现定义的值集的一个离散元素.

它们是相同的,除了提及无关紧要的并发性,并且使用内存位置而不是标量对象,其中

算术类型,枚举类型,指针类型,指向成员类型的指针std::nullptr_t以及这些类型的cv限定版本统称为标量类型.

这不会影响示例.

来自[N4659 expr.ass]

赋值运算符(=)和复合赋值运算符都是从右到左分组.所有都需要一个可修改的左值作为左操作数,并返回一个左值操作数的左值.如果左操作数是位字段,则所有情况下的结果都是位字段.在所有情况下,在右和左操作数的值计算之后,以及在赋值表达式的值计算之前,对赋值进行排序.右操作数在左操作数之前排序.

来自[N3337 expr.ass]

赋值运算符(=)和复合赋值运算符都是从右到左分组.所有都需要一个可修改的左值作为左操作数,并返回一个左值操作数的左值.如果左操作数是位字段,则所有情况下的结果都是位字段.在所有情况下,在右和左操作数的值计算之后,以及在赋值表达式的值计算之前,对赋值进行排序.

唯一的区别是N3337中没有最后一句话.

然而,最后一句话不应该具有任何重要性,因为左操作数i既不是"另一个副作用"也不是 …

c++ language-lawyer c++17

179
推荐指数
3
解决办法
1万
查看次数

拥有空对基类的目的是什么?

libstdc++的 pair 实现有以下奇怪之处

template<typename, typename> class __pair_base
  {
    template<typename T, typename U> friend struct pair;
    __pair_base() = default;
    ~__pair_base() = default;
    __pair_base(const __pair_base&) = default;
    __pair_base& operator=(const __pair_base&) = delete;
  };

template<typename T, typename U>
  struct pair
  : private __pair_base<T, U>
{ /* never uses __pair_base */ };
Run Code Online (Sandbox Code Playgroud)

__pair_base从未使用过,也不能使用,因为它是空的。这是特别令人迷惑,因为std::pair需要是有条件的结构

pair<T, U>如果TU都是结构类型,则是结构类型。

拥有私人基地使其成为非结构性的。

c++ libstdc++

85
推荐指数
1
解决办法
2460
查看次数

有状态的元编程是否形成不良(尚)?

我有幸遇到的最让我最喜爱/最邪恶的发明之一是constexpr计数器,也就是有状态的元编程.正如帖子中所提到的,它似乎在C++ 14下是合法的,我想知道C++ 17有什么变化吗?

以下是主要基于帖子的实现

template <int N>
struct flag
{
    friend constexpr int adl_flag(flag<N>);
    constexpr operator int() { return N; }
};

template <int N>
struct write
{
    friend constexpr int adl_flag(flag<N>) { return N; }
    static constexpr int value = N;
};

template <int N, int = adl_flag(flag<N>{})>
constexpr int read(int, flag<N>, int R = read(0, flag<N + 1>{}))
{
    return R;
}

template <int N>
constexpr int read(float, flag<N>)
{
    return N;
}

template <int N …
Run Code Online (Sandbox Code Playgroud)

c++ metaprogramming language-lawyer c++17

40
推荐指数
1
解决办法
2308
查看次数

std::construct_at 是否使联合的数组成员处于活动状态?

看这个例子(godbolt):

#include <memory>

union U {
    int i[1];
};

constexpr int foo() {
    U u;
    std::construct_at(u.i, 1);
    return u.i[0];
}

constexpr int f = foo();
Run Code Online (Sandbox Code Playgroud)

gcc 和 msvc 成功编译了这个,但 clang 抱怨:

常量表达式中不允许构造没有活动成员的联合体成员“i”的子对象

哪个编译器是正确的?我认为 clang 在这里是错误的,因为 C++20 隐式创建对象(P0593)应该使该程序有效(因为应该隐式创建数组,这应该处于u.i活动状态),但我不确定。

c++ language-lawyer c++20

21
推荐指数
1
解决办法
904
查看次数

为什么0 ==("abcde"+1)不是常量表达式?

为什么下面的代码没有编译?

// source.cpp

int main()
{
   constexpr bool result = (0 == ("abcde"+1));
}
Run Code Online (Sandbox Code Playgroud)

编译命令:

$ g++ -std=c++14 -c source.cpp
Run Code Online (Sandbox Code Playgroud)

输出:

source.cpp: In function ‘int main()’:
source.cpp:4:32: error: ‘((((const char*)"abcde") + 1u) == 0u)’ is not a constant expression
 constexpr bool result = (0 == ("abcde"+1));
                         ~~~^~~~~~~~~~~~~~~
Run Code Online (Sandbox Code Playgroud)

我正在使用gcc6.4.

c++ gcc language-lawyer constexpr c++14

20
推荐指数
1
解决办法
658
查看次数

在C函数中创建的对象的存在

已经建立(见下文)new创建对象需要放置

int* p = (int*)malloc(sizeof(int));
*p = 42;  // illegal, there isn't an int
Run Code Online (Sandbox Code Playgroud)

然而,这是在C中创建对象的一种非常标准的方式.

问题是,int如果是在C中创建并返回到C++ ,是否存在?

换句话说,以下是否保证合法?假设intC和C++是相同的.

foo.h中

#ifdef __cplusplus
extern "C" {
#endif

int* foo(void);

#ifdef __cplusplus
}
#endif
Run Code Online (Sandbox Code Playgroud)

foo.c的

#include "foo.h"
#include <stdlib.h>

int* foo(void) {
    return malloc(sizeof(int));
}
Run Code Online (Sandbox Code Playgroud)

main.cpp中

#include "foo.h"
#include<cstdlib>

int main() {
    int* p = foo();
    *p = 42;
    std::free(p);
}
Run Code Online (Sandbox Code Playgroud)

有关安置强制性质的讨论的链接new:

c c++ object-lifetime language-lawyer

18
推荐指数
1
解决办法
510
查看次数

模板参数中的访问控制

灵感来自这个声称破坏访问控制系统的答案,我编写了以下最小版本的黑客

template<typename T>
inline T memptr{};

template<auto Val>
struct setptr
{
    struct setter { setter() { memptr<decltype(Val)> = Val; } };
    static setter s;
};

template<auto Val>
typename setptr<Val>::setter setptr<Val>::s{};
Run Code Online (Sandbox Code Playgroud)

然后用作

class S
{
    int i;
};

template struct setptr<&S::i>;

auto no_privacy(S& s)
{
    return s.*memptr<int S::*>;
}
Run Code Online (Sandbox Code Playgroud)

为什么不template struct setptr<&S::i>;违反访问控制?

是因为[class.access]

访问控制统一应用于所有名称,无论名称是从声明还是表达式引用.

具体是不包括实例化?在哪种情况下,为什么不包括实例化?

勘误表:显式实例化也被归类为声明.

c++ language-lawyer

17
推荐指数
1
解决办法
243
查看次数

如果不允许使用constexpr,为什么要使用sfinae?

检测习语的工作原理如下

template<typename T, typename = void>
struct has_foo {static constexpr bool value = false;};
template<typename T>
struct has_foo<T, std::void_t<decltype(&T::foo)>> {static constexpr bool value = true;};
template<typename T>
constexpr bool has_foo_v = has_foo<T>::value;
Run Code Online (Sandbox Code Playgroud)

然后我们可以检测出foo任何类型的存在T.

if constexpr(has_foo_v<decltype(var)>)
    var.foo();
Run Code Online (Sandbox Code Playgroud)

我的问题是,键入的内容非常多(读取:想要键入很多键盘),我想知道以下是否可行

if constexpr(std::void_t<decltype(&decltype(var)::foo)>(), true)
    var.foo();
Run Code Online (Sandbox Code Playgroud)

它不是.

这背后有原因吗?
更具体地说,如果允许这样做,还需要做出哪些权衡取舍?

c++ c++17

16
推荐指数
2
解决办法
1421
查看次数

复制空对象是否涉及访问它

灵感来自这个问题.

struct E {};
E e;
E f(e);  // Accesses e?
Run Code Online (Sandbox Code Playgroud)

访问

读取或修改对象的值

空类具有隐式定义的复制构造函数

非联合类的隐式定义的复制/移动构造函数X执行其基础和成员的成员复制/移动.[...]初始化顺序与用户定义构造函数中基数和成员的初始化顺序相同.让我们x为构造函数的任何参数,或者对于移动构造函数,x值指的是参数.以适合其类型的方式复制/移动每个基本或非静态数据成员:

  • [...]基础或成员使用相应的基础或成员进行直接初始化x.

c++ language-lawyer

15
推荐指数
1
解决办法
360
查看次数

T * ...和const T&的部分排序

cppreference要求以下内容

template <class ...T> int f(T*...);  // #1
template <class T>  int f(const T&); // #2
f((int*)0); // OK: selects #1
            // (was ambiguous before DR1395 because deduction failed in both directions)
Run Code Online (Sandbox Code Playgroud)

如果我们遵循DR1395,我们将看到

如果A是从函数参数包转换而P不是参数包,则类型推导失败。否则,使用 使用所得到的类型的P和A,扣然后作为在17.9.2.5 [temp.deduct.type]描述的方法进行。如果P是函数参数包,则将参数模板的每个其余参数类型的类型A与函数参数包的声明符ID的类型P进行比较。每次比较都会为模板参数包中由功能参数包扩展的后续位置推导模板参数。同样,如果A是从功能参数包转换而来的,则将其与参数模板的每个其余参数类型进行比较。 如果对给定类型成功推导,则认为参数模板中的类型至少与参数模板中的类型一样特殊。

[...]

在考虑上述因素后,如果功能模板F至少与功能模板G一样专门,反之亦然,并且G的尾随参数包中F没有对应的参数,并且F没尾随参数包,则F比G更专业。

根据我的推断,这意味着我们应该匹配从T*...const T&反之亦然的每个单独类型。在这种情况下,T*它比const T&TU*成功,T*U失败)更加专业。

但是,编译器不同意。Clang认为它是模棱两可的,gcc认为应该调用第二个,两者都与cppreference不同。

正确的行为是什么?

c++ language-lawyer

15
推荐指数
1
解决办法
1060
查看次数