Click “View Original” to jump to the corresponding file on GitHub, the link is clickable.
QQ group 753792291 for Q&A here.
RSS
Welcome to submit articles/software/resources, etc., leave a message in the comments.
This issue has no sponsorship. It has been a long time since the last update; I have been too lazy, spending my free time playing Street Fighter 6. I finally reached Master rank, so I can play a little less and update the weekly newsletter for everyone.
Everyone, please give some attention, likes, favorites, and rewards to motivate me; without motivation, I can’t write.
However, recently at work, I increasingly feel that language is becoming less important. The evolution of AI in recent years has been astonishing, greatly enhancing productivity, and there are really fewer areas that require learning a language.
News
Updates from the standards committee/IDE/compiler information are placed here.
For the latest compiler information, please follow the hellogcc public account. This week’s update is Issue #288 on 2025-01-08.
Performance Weekly
GCC has accelerated the update of reflection implementation link.
A significant productivity liberation tool has finally arrived.
Additionally, there have been meetings recently, but nothing new.
Trip report: November 2025 ISO C++ standards meeting Kona
Summary by Teacher Herb.
Articles
Trip report: Meeting C++ 2025
Still contract, nothing interesting.
Virtual Trip Report: WG21 Kona 2025
Introduced proposals of interest from the Kona meeting.
Patch for SIMD targeting complex types.
Understanding std::constant_arg, previously std::nontype_t.
// libfoo.h:
int foo(std::function_ref<int(float)>);
// app.cpp:
void something() {
// Store lambda object -> indirect call:
foo([](float x) { return int(x * 0.5); });
// Function pointer is nullptr -> direct call:
foo(std::constant_arg<[](float x) { return int(x * 0.5); }>);
}
C++ Enum Class and Error Codes
Enum types cannot be implicitly converted to bool.
const auto ret = cpp::some_operation( ... );
if ( ret ) // Compilation fails! enum class cannot implicitly convert to bool
if ( ret == cpp::result::Success ) // Works, but more verbose, I don't like typing so much.
Can we encapsulate it externally?
enum class Result {
Success = 0,
SomeError,
SomeOtherError
};
// Compilation fails! Cannot define operator bool as a static function
inline explicit operator bool(Result r) { return r != Result::Success; }
Of course, AI might make you write two exclamation marks.
inline bool operator!(Result r) { return r == Result::Success; }
void foo()
{
const Result ret = cpp::some_operation( ... );
if ( !!ret )
{
// Handle error
}
}
It’s too unreasonable; we consider putting it inside the type.
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 ) {} // Works
if ( ret.v == 42 ) {} // Compilation error
}
Still need to write such a long scope prefix, what to do?
Inline it internally.
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;
// Repeat this for each value in the enum.
};
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 ) {} // Works
if ( ret == Result::SomeError ) {} // Also works
if ( ret.v == 42 ) {} // Compilation error
}
Now it works, but the dirty work is a lot; I say, let AI write it.
AI looks at code, AI writes code, a hands-off manager, might directly use two exclamation marks as they please.
Automated Equality Checks in C++ with Reflection (C++26)
Simple equality comparison using C++ reflection.
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;
}
The principle of this code is as follows:
- • ^^T is a reflection operator that generates a std::meta::info type value representing the meta-information of type T itself.
- • std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()) queries all non-static data members of type T, returning a reflection value array ordered by declaration. The unchecked access context deliberately bypasses access control (private/protected), allowing private members to be compared.
- • std::define_static_array solidifies this runtime-unknown reflection collection into a compile-time known static array, making it possible to iterate at compile time.
- • template for is a compile-time loop that unfolds at compile time, iterating once for each member reflection value mem.
- • a.[:mem:] is a splicing expression, which is the inverse operation of ^^T: ^^ enters the “metaverse” to obtain type information, while [: :] returns from the metaverse to the standard C++ syntax world, dynamically splicing out the access expression for members.
Full code link.
A prvalue is not a temporary
Pure rvalues are values that will only convert to objects when needed.
Sounds like nonsense.
Efficient C++: The hidden compile-time cost of auto return types
Using auto in header files can increase compile time; you can generate trace files for analysis using -ftime-trace.
Looking at binary trees in C++
Conventional binary tree implementations, including those written by AI, are of the double pointer type.
class TreeNode {
public:
int data;
TreeNode* left;
TreeNode* right;
TreeNode(int value) : data(value), left(nullptr), right(nullptr) {}
};
Poor locality; the author uses an array to store indices to improve it.
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);
} elseif (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]);
}
};
Good locality, metadata is concentrated, the more data, the better the effect, performance improvement of 30% or more.
Speeding up C++ functions with a thread_local cache
In old code, there are those that traverse maps and are hard to modify; how to speed up access, using thread_local cache index.
Feels a bit difficult to evaluate; if possible, it’s best to rewrite instead of piling patches on patches.
Using RAII to remedy a defect where not all code paths performed required exit actions, follow-up
Sometimes RAII + lambda pattern is needed, but if the receiver copies the lambda, the RAII lambda might be executed twice due to bad luck; how to solve this problem?
Wrap the lambda in a shared_ptr.
When Compiler Optimizations Hurt Performance
This is a case on AArch64 clang.
Code
#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; // Invalid leading byte
}
}
Poor performance; the reason is that the compiler optimizes it into a jump table.
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
Using clang++ -O2 -fno-jump-tables –std=c++20 can break this.
GCC does not generate a jump table, so it does not have this problem.
Functions are asymmetric
In traditional programming, all functions must return “one” value—even if they can accept any number of parameters.
This “many-to-one” structure is asymmetric. Even if we consider void as “a return value” (representing “no value”), it does not change the fact that the number of return values is always one.
Functional languages (like Haskell) achieve symmetry by currying, transforming all functions into “one input, one output”. However, mainstream languages like C++ lack native support for this paradigm and are more accustomed to using multi-parameter functions.
Another idea is to pack multiple return values into a tuple, but this is cumbersome and blurs the semantic boundary between “returning a structure” and “returning multiple independent values”.
The author introduces a conversion structure.
template <callable ...Fs, typename ...Args>
auto call_by_need(some<Fs...>, some<Args...>);
How to implement it? Readers can let AI write one.
The case against Almost Always auto
Auto hides type ownership, and the hidden information must be well-named, but naming is difficult.
There is some truth to this, but now AI writes code without auto; I, as a lazy dog, use auto.
It depends on the situation; try not to use it. If the name is too long to bear, you can use auto.
However, the auto IDE can also help you refactor into specific types.
Discovering observers – part 1
Review how to write the observer pattern; simply put, it involves calling external hooks along the call path.
External hooks can be multiple, which involves managing observer subscriptions and cancellations. But most scenarios are not that complex.
Here’s a code example link.
#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
Changes in C++26.
Support for maybe_unused.
std::pair xy { 42.3, 100.1 };
auto [x, y [[maybe_unused]]] = xy;
std::print("{}", x);
Placed in conditions.
#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");
}
Converted into a pack, very cool.
auto [head, ...rest] = std::tuple{1,2,3,4};
// head = 1, rest... expands to (2,3,4)
Very cool.
// 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);
Supports constexpr.
int main() {
constexpr auto [a, b] = std::tuple{2, 3};
static_assert(a * b == 6);
}
PXXXXR0: Alternative free function call syntax
Just for fun, UCFS has been proposed long ago, but could not be advanced due to historical debts.
How to Avoid Thread-Safety Cost for Functions’ static Variables
Static variables in functions only have one to ensure thread safety, but enabling -fno-threadsafe-statics can generate multiple; does this guarantee have overhead? Basically none.
Cuckoo hashing improves SIMD hash tables (and other hash table tradeoffs)
Improvements are still not as fast as swisstable.
The Performance Spectrum
Programmers from different language backgrounds have different tolerances for performance slowness; what they see as slow in C++ may not be a big deal to them.
Simplifying variant use
The author implements operator & to simplify visitors.
See the code.
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>>>
decay(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>
>>
decay(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{};
}
By using visitor, the code can be simplified to:
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));
}
Saving time on writing lambda parentheses.
Godbolt link.
3rd Largest Element: SIMD Edition
SIMD section, I won’t elaborate here; if interested, check it out yourself.
Raymond Chen’s articles are at the end; I don’t understand 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
Open Source Project Introduction
- • Asteria is an embeddable scripting language, looking for contributors for a long time. I hope friends can help, and you can also join group 753302367 to discuss with the author.