C++ 中文周刊 2025-11-15 第189期

周刊项目地址

公众号

点击「查看原文」跳转到 GitHub 上对应文件,链接就可以点击了

qq群 753792291 答疑在这里

RSS

欢迎投稿,推荐或自荐文章/软件/资源等,评论区留言

本期文章没有赞助 好久没更新了,是在是太懒,空闲时间都用来打街霸6了,终于上了大师, 可以少玩一点了,给家人们更新周刊

家人们点点关注点点赞收藏打赏什么的,给点动力,没动力写不动

不过最近工作,愈发觉得语言更不重要了,AI这几年的进化太夸张了,生产力提升太高,涉及到学语言的地方真的少了


资讯

标准委员会动态/ide/编译器信息放在这里

编译器信息最新动态推荐关注hellogcc公众号 本周更新 2025-01-08 第288期

性能周刊

GCC已经加快更新了反射实现 https://gcc.gnu.org/pipermail/gcc-patches/2025-November/700733.html

重大生产力解放工具,总算来了

另外最近也在开会,没啥新东西

Trip report: November 2025 ISO C++ standards meeting kona

草药老师的纪要

文章

Trip report: Meeting C++ 2025

还是contract,没啥意思

Virtual Trip Report: WG21 Kona 2025

介绍了kona会上这个哥们感兴趣的提案

simd针对complex类型打补丁

理解std::constant_arg,以前是std::nontype_t

// libfoo.h:
int foo(std::function_ref<int(float)>);

// app.cpp:
void something() {
  // 存储 lambda 对象 -> 间接调用:
  foo([](float x) { return int(x * 0.5); });

  // 函数指针为 nullptr -> 直接调用:
  foo(std::constant_arg<[](float x) { return int(x * 0.5); }>);
}

C++ Enum Class and Error Codes

枚举类型无法隐式转成bool

const auto ret = cpp::some_operation( ... );

if ( ret ) // 编译失败!enum class 无法隐式转换为 bool

if ( ret == cpp::result::Success )  // 可以工作,但更繁琐,我不喜欢打这么多字

能不能外部封装一下?

enum class Result {
    Success = 0,
    SomeError,
    SomeOtherError
};
// 编译失败!不能将 operator bool 定义为静态函数
inline explicit operator bool(Result r) { return r != Result::Success; } 

当然AI可能会让你写两个感叹号

inline bool operator!(Result r) { return r == Result::Success; }

void foo()
{
  const Result ret = cpp::some_operation( ... );
  if ( !!ret ) 
  {
    // 处理错误
  }
}

太不合理了,我们考虑放到类型内部

struct Result {
    enum class Value {
        Success = 0,
        SomeError,
        SomeOtherError
    } v;

    explicit operator bool() const { return v != Result::Value::Success; }
};

void bar()
{
    const Result ret = cpp::some_operation( /* ... */ );
    if ( ret.v == Result::Value::SomeError ) {} // 可以工作
    if ( ret.v == 42 ) {} // 编译错误
}

还要写那么长的作用域前缀,怎么搞

内部inline一下

struct Result {
    enum class Value {
        Success = 0,
        SomeError,
        SomeOtherError
    } v;

    constexpr Result( Value x ) : v( x ) {}
    constexpr explicit operator bool() const { return v != Result::Success; }

    static constexpr Value Success = Value::Success;
    static constexpr Value SomeError = Value::SomeError;
    static constexpr Value SomeOtherError = Value::SomeOtherError;
    // 对枚举中的每个值重复此操作
};

inline constexpr bool operator==( Result lhs, Result rhs ) { return lhs.v == rhs.v; }
inline constexpr bool operator!=( Result lhs, Result rhs ) { return lhs.v != rhs.v; }

void foo()
{
    const Result ret = cpp::some_operation( /* ... */ );
    if ( ret ) {} // 可以工作
    if ( ret == Result::SomeError ) {} // 同样可以工作
    if ( ret.v == 42 ) {} // 编译错误
}

能用了,现在的问题是,脏活很多,要我说,就AI写就好了

AI看代码AI写代码,甩手掌柜,可能直接两个感叹号爱用不用

Automated Equality Checks in C++ with Reflection (C++26)

简单用c++反射实现相等比较

template<typename T>
bool compare_same(const T& a, const T& b) {
    template for (constexpr auto mem : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
        if (!compare_same(a.[:mem:], b.[:mem:])) {
            return false;
        }
    }
    return true;
}

这段代码的原理如下:

完整代码 https://godbolt.org/z/er8b4GbE5

A prvalue is not a temporary 纯右值不是临时对象

纯右值是值,只有在需要转换的时候才会转成对象

听起来像废话

Efficient C++: The hidden compile-time cost of auto return types

头文件中的auto会使编译时间增加,可以通过 -ftime-trace 生成 trace文件分析

Looking at binary trees in C++

常规的二叉树写法,包括AI写的,都是双指针类型

class TreeNode {
public:
    int data;
    TreeNode* left;
    TreeNode* right;

    TreeNode(int value) : data(value), left(nullptr), right(nullptr) {}
};

局部性很差,作者用数组存索引来改善

class BinaryTreeOptional {
private:
    int root = -1;
    std::vector<std::optional<TreeNodeOptional>> nodes;

    size_t insertRecursive(int node, int value) {
        if (node == -1) {
            nodes.emplace_back(value);
            return nodes.size() - 1;
        }

        auto node_value = nodes[node]->data;
        if (value < node_value) {
            nodes[node]->left = insertRecursive(nodes[node]->left, value);
        } else if (value > node_value) {
            nodes[node]->right = insertRecursive(nodes[node]->right, value);
        }

        return node;
    }

    void inOrderRecursive(std::optional<TreeNodeOptional>& onode) {
        if (onode.has_value()) {
            auto& node = onode.value();
            if (node.left != -1)
                inOrderRecursive(nodes[node.left]);
            if (node.right != -1)
                inOrderRecursive(nodes[node.right]);
        }
    }

public:
    void insert(int value) {
        root = insertRecursive(root, value);
    }

    void inOrderTraversal() {
        if (root != -1)
            inOrderRecursive(nodes[root]);
    }
};

局部性好,元数据集中,数据量越多效果越好,性能提升30%+

Speeding up C++ functions with a thread_local cache

老代码中有那种遍历map还不好改的代码,如何加速访问,thread_local缓存index

感觉有点难评,能重写最好重写,不要补丁摞补丁

Using RAII to remedy a defect where not all code paths performed required exit actions, follow-up

有时候需要RAII+lambda模式,但如果接收方复制了lambda,由于RAII lambda被倒霉的执行两次,如何解决这个问题?

lambda用shared_ptr包一层

When Compiler Optimizations Hurt Performance

这是一个在AArch64 clang上的案例

代码

#include <bit>
int utf8_sequence_length(unsigned char lead_byte) {
    switch (std::countl_one(lead_byte)) {
        case 0: return 1;
        case 2: return 2;
        case 3: return 3;
        case 4: return 4;
        default: return 0; // 无效首字节
    }
}

性能不佳,原因是编译器帮忙优化成跳转表了

adrp    x9, .Lswitch.table.utf8_sequence_length(unsigned char)
add     x9, x9, :lo12:.Lswitch.table.utf8_sequence_length(unsigned char)
ldr     w0, [x9, w8, uxtw #2]
...
.Lswitch.table.utf8_sequence_length(unsigned char):
        .word   1
        .word   0
        .word   2
        .word   3
        .word   4

使用 clang++ -O2 -fno-jump-tables –std=c++20可破

gcc没有生成跳转表,所以没有这个问题

函数是不对称的

在传统编程中,所有函数都必须返回“一个”值——即使它们可以接受任意数量的参数

这种“多入一出”的结构是不对称的。即使我们将 void 视为“一种返回值”(代表“无值”),也无法改变“返回值数量恒为一”的事实。

函数式语言(如 Haskell)通过柯里化(currying) 将所有函数转化为“一元输入、一元输出”,从而实现对称性。但 C++ 等主流语言缺乏对这种范式的原生支持,更习惯使用多参数函数。

另一种思路是将多个返回值打包成 tuple,但这繁琐且模糊了“返回一个结构”与“返回多个独立值”的语义边界

作者引入一个转换结构

template <callable ...Fs, typename ...Args>
auto call_by_need(some<Fs...>, some<Args...>);

怎么实现,读者拿AI写一个

The case against Almost Always auto

auto隐藏了类型所有权,隐藏的信息被迫要好好命名,但命名很难

说的有点道理,不过现在AI写代码不auto,我这样的懒狗才auto

是情况而定,尽量不用,名字特长无法忍受可以auto

不过auto IDE也能帮你重构成具体的类型

Discovering observers - part 1

回顾一下观察者模式咋写,简单说,就是调用路径调用一下外部的钩子

外部钩子可以多个,也就涉及到观察者订阅取消管理。但大部分场景没那么复杂

贴一个代码例子 https://godbolt.org/z/Mv7YMM3aK

#include <iostream>
#include <string_view>
#include <vector>

template <typename Message>
class Subscriber {
   public:
    virtual ~Subscriber() = default;
    virtual void update(Message message) = 0;
};

template <typename Message>
class Publisher {
   public:
    virtual ~Publisher() = default;
    void subscribe(Subscriber<Message>* subscriber) {
        std::cout << "Got a new subscriber\n";
        _subscribers.push_back(subscriber);
    }

    void unsubscribe(Subscriber<Message>* subscriber) {
        std::cout << "Someone unsubscribed\n";
        std::erase(_subscribers, subscriber);
    }

   protected:
    void notify(Message message) const {
        std::cout << "Sending an update to " << _subscribers.size()
                  << " subscriber(s)\n";
        for (auto* const subscriber : _subscribers) {
            notifyOne(subscriber, message);
        }
    }

   private:
    virtual void notifyOne(Subscriber<Message>* const,
                           Message message) const = 0;

    std::vector<Subscriber<Message>*> _subscribers;
};

using SettingsMessage = std::pair<std::string, int>;
class SettingsSubscriber : public Subscriber<SettingsMessage> {
   public:
    void update(std::pair<std::string, int> message) override {
        std::cout << "Subscriber is getting an update:\n"
                  << message.first << "=" << message.second << '\n';
    }
};

class SettingsPublisher : public Publisher<SettingsMessage> {
   public:
    void notifyOne(Subscriber<SettingsMessage>* const subscriber,
                   SettingsMessage message) const override {
        subscriber->update(message);
    }

    void setSetting1(int value) {
        _setting1 = value;
        notify({"setting1", _setting1});
    }

    void setSetting2(int value) {
        _setting2 = value;
        notify({"setting2", _setting2});
    }

   private:
    int _setting1{0};
    int _setting2{0};
};

int main() {
    SettingsPublisher pub;
    SettingsSubscriber s1, s2;
    pub.subscribe(&s1);
    pub.subscribe(&s2);
    pub.setSetting1(42);
    pub.unsubscribe(&s1);
    pub.setSetting1(51);
}

/*
Got a new subscriber
Got a new subscriber
Sending an update to 2 subscriber(s)
Subscriber is getting an update:
setting1=42
Subscriber is getting an update:
setting1=42
Someone unsubscribed
Sending an update to 1 subscriber(s)
Subscriber is getting an update:
setting1=51
*/

Structured bindings in C++17, 8 years later

c++26的改动

支持 maybe_unused

std::pair xy { 42.3, 100.1 };
auto [x, y [[maybe_unused]]] = xy;
std::print("{}", x);

放在条件中

#include <print>

struct Point {
    int x { 0 };
    int y { 0 };
    explicit operator bool() const noexcept { return x > 0; }
};
Point createPoint(int a) { return Point { 10*a, 10*a }; }

int main() {
    if (auto [x, y] = createPoint(100))
        std::print("point is true");
}

转成一个pack,非常炫酷

auto [head, ...rest] = std::tuple{1,2,3,4};
// head = 1, rest... expands to (2,3,4)

炫酷

// Generic dot product for any tuple-like / array / aggregate supported by structured bindings.
template <class P, class Q>
constexpr auto dot_product(const P& p, const Q& q) {
    // Bind all elements of each input into packs.
    const auto& [...ps] = p;
    const auto& [...qs] = q;

    // (Optional) sanity check: both must have the same size
    static_assert(sizeof...(ps) == sizeof...(qs), "Mismatched sizes");

    // Elementwise multiply, then fold with +
    return ((ps * qs) + ...);
}

constexpr std::array<int, 3> a{1, 2, 3};
constexpr std::array<int, 3> b{4, 5, 6};
static_assert(dot_product(a, b) == 32);

支持constexpr

int main() {
    constexpr auto [a, b] = std::tuple{2, 3};
    static_assert(a * b == 6);
}

PXXXXR0: Alternative free function call syntax

看一乐,UCFS很早就有人提了,不过推进不了,历史债太多

How to Avoid Thread-Safety Cost for Functions’ static Variables

函数中的静态变量为了保证线程安全只有一个,但是开启 -fno-threadsafe-statics 可以生成多个,这个保证有开销吗?基本没有

Cuckoo hashing improves SIMD hash tables (and other hash table tradeoffs)

改进也没swisstable快

The Performance Spectrum

不同语言的背景的程序员对于性能慢的接受程度不同,c++的遇到慢的点在他们看来可能不算个事情

Simplifying variant use

作者实现 operator &来简化visitor

看代码

namespace vpp {
namespace detail {

template<typename T>
struct is_specialization_of_variant : std::false_type {};

template<typename... Args>
struct is_specialization_of_variant<std::variant<Args...>> : std::true_type {};

template<typename T>
struct is_variant : is_specialization_of_variant<std::remove_cv_t<std::remove_reference_t<T>>> {};

template<typename T>
constexpr bool is_variant_v = is_variant<T>::value;

template<class... Ts>
struct overload : Ts... {
    using Ts::operator()...;
};

// Visitor wrapper
template<class Over>
struct visitor_t {
private:
    Over over;

public:
    explicit visitor_t(Over o) : over(std::move(o)) {}

    // Compose another functor
    template<typename U>
    auto operator&(U&& u) const {
        using UDec = std::decay_t<U>;
        return visitor_t<detail::overload<Over, UDec>>{
            detail::overload<Over, UDec>{ over, std::forward<U>(u) }
        };
    }

    // Single-variant visit
    template<typename V, typename = std::enable_if_t<detail::is_variant_v<V>>>
    decltype(auto) operator()(V&& v) const {
        return std::visit(over, std::forward<V>(v));
    }

    // Multi-variant visit
    template<typename V1, typename V2, typename... Vs,
             typename = std::enable_if_t<
                 detail::is_variant_v<V1> && detail::is_variant_v<V2>
             >>
    decltype(auto) operator()(V1&& v1, V2&& v2, Vs&&... vs) const {
        return std::visit(over, std::forward<V1>(v1), std::forward<V2>(v2), std::forward<Vs>(vs)...);
    }
};


} // namespace detail


constexpr inline struct {
    template<typename U>
    auto operator&(U&& u) const {
        using UDec = std::decay_t<U>;
        return detail::visitor_t<detail::overload<UDec>>{ detail::overload<UDec>{ std::forward<U>(u) } };
    }
} visitor{};

}

通过visitor,代码可以简化成

void example_unary_visitor() {
    std::variant<int, double, std::string> v1 = 42;
    std::variant<int, double, std::string> v2 = 3.14;
    std::variant<int, double, std::string> v3 = "Hello";

    auto to_string_visitor = vpp::visitor
        & [](int x) { return std::to_string(x); }
        & [](double d) { return std::to_string(d); }
        & [](const std::string& s) { return s; };

    std::println("[Unary visitor example]");
    std::println("v1: {}", to_string_visitor(v1));
    std::println("v2: {}", to_string_visitor(v2));
    std::println("v3: {}", to_string_visitor(v3));
}

,省几个写lambda括号的时间

godbolt https://cpp2.godbolt.org/z/7x3sf9KoW

3rd Largest Element: SIMD Edition

simd 环节,这里就不展开了,感兴趣自己看一下

raymondchen文章放在后面,我不懂windows

What to do when you have a crash in the runtime control flow guard check

Trying to build a XAML tree in code throws a “No installed components were detected” exception

开源项目介绍

工作招聘

微信公众号里发了很多,感兴趣的可以看看


上一期

本期

下一期

看到这里或许你有建议或者疑问或者指出错误,请留言评论! 多谢! 你的评论非常重要!也可以帮忙点赞收藏转发!多谢支持! 觉得写的不错那就给点吧, 在线乞讨 微信转账