PAYATHON 2026

MSVC 的 consteval 成员指定初始化为何报 C7595?

数据小陈

结论

按 C++20 的相关规则,这个最小示例应当合法。结合所述编译现象,可以判断问题出在 MSVC 对这类初始化的处理上。示例没有使用任何标准库设施,因此无需从 Microsoft 标准库中查找原因。

这一判断针对题目中的完整示例。仅凭 toolset 143,无法确定受影响的具体编译器修订号,也不能断言某个版本已经修复了该问题。

.ceval = 1 会调用什么

HasConstEval 没有用户声明的构造函数,满足这里的 aggregate 条件。成员 ConstEval 有构造函数,不妨碍外层 HasConstEval 成为 aggregate。

指定初始化中的 .ceval = 1 用 1 初始化成员 ceval,会选择非 explicit 的转换构造函数 ConstEval(int)。其中的 = 是初始化语法,不是给已经构造的对象赋值。依据是 C++20 工作草案的 aggregate 初始化规则。

ConstEval(int) 声明为 consteval,是 immediate function,因此这次调用需要满足常量表达式的要求。示例中传入的是整数字面量 1,构造函数只用它初始化 int a,不读取运行期数据,也没有其他阻止常量求值的操作,能够满足这一要求。相关定义见 consteval 说明和 immediate invocation 规则。

局部变量 x 不需要因此声明为 constexpr。受强制常量求值约束的是 immediate invocation,这不会自动使接收初始化结果的对象成为常量对象。

如何绕过当前编译器的问题

可以先显式构造一个编译期常量,再用它初始化成员。这样,指定初始化处会使用 ConstEval 的隐式复制构造函数:

struct ConstEval {
    consteval ConstEval(int x) : a{ x } {}
    int a;
};

struct HasConstEval {
    ConstEval ceval;
};

int main()
{
    static constexpr ConstEval initial{1};
    static_assert(initial.a == 1);

    HasConstEval x{
        .ceval = initial
    };

    x.ceval.a = 2;
}

这种写法保留了 HasConstEval 的 aggregate 形式和 consteval 构造函数。initial 是常量,复制得到的 x.ceval 仍然可以修改。它是否能避开所用 MSVC 修订版的问题,还需要在对应环境中验证。

排查时可以按以下顺序处理:

  1. 确认启用了 /std:c++20 或更高语言模式,并记录完整的 MSVC 编译器版本及编译选项。
  2. 在原环境中验证上述写法,确认能否保留现有接口并绕过错误。
  3. 条件允许时,用更新的 MSVC 编译器重新编译原始最小示例,再决定是否移除兼容写法。

修改构造函数时要注意什么

给 HasConstEval 添加用户声明的构造函数后,它在 C++20 中将不再满足 aggregate 的条件,也就不能继续使用原来的 designated initializer。这样修改可以改变编译器处理初始化的路径,但不能说明原始写法违反标准。aggregate 的定义中包含这一限制。

将 ConstEval(int) 从 consteval 改成 constexpr 也会改变接口约束:constexpr 允许满足条件的调用在编译期求值,也允许运行期调用。只有业务允许这种变化时,才适合采用这个方案。

备注:内容仅供参考。