【C++23】(二)if consteval 与 Deducing this:更多编译期能力

C++23 让”编译期计算”和”对象方法调用”都变得更强大。if consteval 让你在编译期分支判断,而 deducing this 则彻底改变了成员函数的设计方式——不再需要模板就能实现”泛型方法”。


一、if consteval:编译期还是运行期?

痛点回顾

在 C++20 引入 consteval 之后,我们可以用它声明”必须在编译期求值”的函数:

1
2
3
4
consteval int square(int n) { return n * n; }
static_assert(square(3) == 9); // ✅ 编译期计算
int x = 5;
// square(x); // ❌ 运行时值不能用于 consteval 函数

问题来了:如果你有一个函数,想让它在”编译期返回 42,运行时返回 0”——在 C++23 之前,你无法判断当前是否处于常量求值上下文。

if consteval 的解决方案

1
2
3
4
5
6
7
8
9
10
11
// C++23
consteval int check() {
if consteval {
return 42; // 编译期执行这条
} else {
return 0; // 运行时执行这条
}
}

static_assert(check() == 42); // 编译期调用,走 if 分支
int runtime_val = check(); // 运行时调用,走 else 分支

if consteval专门用于常量求值上下文的 if 语句,它的条件部分没有括号——这是 C++23 的新语法。

实际应用:编译期vs运行期差异化实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
consteval auto process(int n) {
if consteval {
return "编译期字符串";
} else {
return "运行时字符串";
}
}

// 用于模板元编程
template<int N>
struct Wrapper {
static constexpr auto value = []{
if consteval {
return N * 2;
} else {
return N + 10;
}
}();
};

二、Deducing this:成员函数的革命

传统方式的局限性

1
2
3
4
5
6
7
8
struct Widget {
int value = 100;

// 传统写法:隐式 this 指针
void print() {
std::cout << "Widget: " << value << "\n";
}
};

问题

  1. print() 只能被 Widget 调用,无法泛化到其他有 value 成员的类
  2. 菱形继承中,this 指针调整容易出错
  3. 实现 CRTP 等模式时语法繁琐

显式对象参数语法

C++23 允许你用 this 作为显式对象参数(explicit object parameter)

1
2
3
4
5
6
7
8
struct Widget {
int value = 100;

// 显式对象参数:self 是 *this 的拷贝
void print(this Widget& self) {
std::cout << "Widget: " << self.value << "\n";
}
};

语义:调用 w.print() 时,self 会绑定到 w。这不是模板,却实现了”方法可被其他类型复用”的能力。

模板版本:真正的泛型方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct Widget {
int value = 100;

// 模板版本:接收任何类型
template<typename Self>
void print_tmpl(this Self& self) {
std::cout << "value: " << self.value << "\n";
}
};

struct Gadget { int value = 200; };

int main() {
Widget w;
w.print(); // ✅ 正常工作

Gadget g;
g.print_tmpl(); // ✅ 也能调用!只要有 value 成员
}

这就是 deducing this 的魔力——一个非模板函数,却能自动推导调用者的类型。


三、完整示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>

// if consteval (C++23)
consteval int check() {
if consteval {
return 42; // 编译期
} else {
return 0; // 运行时
}
}

// deducing this (C++23)
struct Widget {
int value = 100;

// 显式对象参数: this 推导
void print(this Widget& self) {
std::cout << "Widget: " << self.value << "\n";
}

// 模板版本: 接收任何类型
template<typename Self>
void print_tmpl(this Self& self) {
std::cout << "value: " << self.value << "\n";
}
};

int main() {
static_assert(check() == 42);

Widget w;
w.print(); // C++23 style

struct Gadget { int value = 200; };
Gadget g;
g.print_tmpl(); // 泛化: Gadget也有value成员即可
}

输出:

1
2
Widget: 100
value: 200

四、Deducing this 的应用场景

场景 1:简化实现继承中的方法转发

1
2
3
4
5
6
7
8
struct Base {
template<typename Self>
void common(this Self& self) {
// 基类方法现在可以访问任何子类的成员
}
};

struct Derived : Base {};

场景 2:解决菱形继承问题

1
2
3
4
5
6
7
8
struct A { int x; };
struct B : A {};
struct C : A {};

// 传统方式需要虚继承
struct D : B, C {};

// deducing this 更自然地处理 this 调整

场景 3:泛型方法的简洁实现

1
2
3
4
5
6
7
8
9
struct Data {
std::vector<int> values;

// 一个方法,同时支持拷贝和移动
template<typename Self>
auto sum(this Self& self) {
// self 既可以是 Data& 也可以是 Data&&
}
};

五、if consteval vs __builtin_is_constant_evaluating

特性if consteval__builtin_is_constant_evaluting
标准C++23 标准GCC/Clang 扩展
可移植性✅ 通用❌ 仅 GCC/Clang
语法if consteval { }if (__builtin_is_constant_evaluting())
上下文判断精确近似

建议:始终使用 if consteval,它是标准写法,语义也更清晰。


总结if constevaldeducing this 是 C++23 最重要的两大手册(handbook)改进。前者让编译期分支判断成为标准语法,后者让成员函数摆脱了”只能被单一类型调用”的束缚。如果你写过模板元编程或处理过复杂的继承关系,你会立即爱上这两个特性。


📚 C++23 新特性 系列导航

本文是《C++23 新特性》系列第 2/4 篇。

方向章节
◀ 上一篇(一)std::expected
下一篇 ▶(三)std::print / to_underlying
📖 全部 4 篇目录(点击展开)
  1. (一)std::expected
  2. (二)if consteval 与 Deducing this ← 当前
  3. (三)std::print / to_underlying
  4. (四)Ranges 增强

语法/控制流可视化

下面是一张马卡龙色 Mermaid 图,帮你从图形角度把握本章涉及的语法与控制流。

%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#FFE5EC', 'primaryTextColor': '#5D5D5D', 'primaryBorderColor': '#FFB3C6', 'lineColor': '#B5EAD7', 'secondaryColor': '#C7CEEA', 'tertiaryColor': '#FFDAC1'}}}%%
flowchart TD
    CALL["📞 foo(x)"] --> DT{"🪄 deducing this<br/>推导 Self"}
    DT --> S1["Self = Foo"]
    DT --> S2["Self = const Foo"]
    DT --> S3["Self = Foo&"]

    S1 --> BODY["🧩 进入函数体<br/>self.member"]
    S2 --> BODY
    S3 --> BODY

    BODY --> CV{"🤔 if consteval?"}
    CV -- "编译期调用" --> CE["⚙️ 编译期分支<br/>编译期求值"]
    CV -- "运行期调用" --> RE["🏃 运行期分支<br/>运行时执行"]

    style CALL fill:#FFE5EC,stroke:#FFB3C6,color:#5D5D5D
    style DT fill:#FFDAC1,stroke:#FFB3C6,color:#5D5D5D
    style S1 fill:#C7CEEA,stroke:#A8DADC,color:#5D5D5D
    style S2 fill:#C7CEEA,stroke:#A8DADC,color:#5D5D5D
    style S3 fill:#C7CEEA,stroke:#A8DADC,color:#5D5D5D
    style BODY fill:#B5EAD7,stroke:#A8DADC,color:#5D5D5D
    style CV fill:#FFDAC1,stroke:#FFB3C6,color:#5D5D5D
    style CE fill:#FFE5EC,stroke:#FFB3C6,color:#5D5D5D
    style RE fill:#C7CEEA,stroke:#A8DADC,color:#5D5D5D

本章讲 C++23 的 if consteval 与 deducing this,对比维度:本特性 vs C++17/20 旧写法 vs 其他语言对应特性。

对比分析

一、本特性 vs 旧写法

维度C++23 写法C++20 写法影响
编译期函数与运行期分支if consteval { ... } else { ... }需要用 std::is_constant_evaluated()写法更直白
泛型成员函数void foo(this Self& self) {...}需要 CRTP / std::enable_if不再需要模板也能泛型
与 lambda 结合deducing this lambda模板 lambda统一

二、对比其他语言

语言编译期与运行期分支泛型方法备注
C++23if constevaldeducing this显式
Rustconst fn + 调用点分析trait + Self不同思路
Javabounded generics受限
Pythonduck typing运行时
C#generic methods较受限

三、优缺点

优点:

  • if consteval 终于比 is_constant_evaluated() 更优雅
  • deducing this 把”成员函数当模板”标准化

缺点:

  • 编译器支持普及度仍需观察
  • 与既有 CRTP 代码如何迁移需谨慎

四、何时选

  • 写库代码:直接用 deducing this 替代 CRTP
  • 工具函数:if consteval 让意图明确