Dec 15, 2025

[C++] [CppCon 2025] Implement the C++ Standard Library minute

Reference:

Implement the C++ Standard Library: Design, Optimisations, Testing while Implementing Libc++
[C++] Object Lifetimes reading minute

1)

1) tail padding.
2) if type is empty, it does not acquire address.

e.g.
struct Foo {
  int a;
  char c;
  bool b;
};

enum class ErrCode : int {
 E1, E2, E3,
};

template*<typename Val, Err>
struct Expected {
  union U {
  	[[no_unique_address]] Val val_;
	[[no_unique_address]] Err err_;
  };
  [[no_unique_address]] U value_;
  bool has_value_; 
};
Without `no_unique_address`, no tail padding,
sizeof(Expected<Foo, ErrCode>) == 12

With `no_unique_address`, 
bool has_value_;
can sneak into
U if U is of type Foo, `has_value_` would occupy Foo's tail padding,
thus:
sizeof(Expected<Foo, ErrCode>) == 8


2)

template<typename Val, Err>
struct Expected {
  union U {
  	[[no_unique_address]] Val val_;
	[[no_unique_address]] Err err_;
  };
  [[no_unique_address]] U value_;
  bool has_value_; 
  
  Expected(const Expected& other) :
  	has_value{other.has_value} {
	 if (has_value_) {
	 	std::construct_at(std::addressof(value_.val_), other.value_.val_);
	 } else {
	 	std::construct_at(std::addressof(value_.err_), other.value_.err_);
	 }
	}
};

Expected e1 = {Foo{}};
Expected e2 = e1;
assert(e2.has_value_); // Failed.
Why failed? Due to std::construct_at construct the value_ union's val_ and
zero out the padding, which has_value_ resides in due to `no_unique_address`.

Take away:

Don't mix no_unique_address with manual lifetime management
(union, construct_at, placement-new, std::bit_cast, std::start_lifetime_as, std::start_lifetime_as_array).


3)

Reference:
Segmented Iterators and Hierarchical Algorithms - Matthew H. Austern
https://lafstern.org/matt/segmented.pdf
Which is faster?
std::deque<int> d = ...

// 1
for (int& i : d) {
    i = std::clamp(i, 200, 500);
}

// 2 Faster due to using Segmented Iterators
std::ranges::for_each(d, [](int& i) {
    i = std::clamp(i, 200, 500);
});

Segmented Iterators:

template <class T> struct seg_array_iter
{
    // Local Iterator: position within the current segment (e.g., a vector<T>::iterator)
    vector<T>::iterator cur;
    
    // Segment Iterator: position among the segments (e.g., a vector<vector<T>*>::iterator)
    vector<vector<T>*>::iterator node;

    T& operator*() const { return *cur; }

    seg_array_iter& operator++() {
        // Standard increment: move to the next element in the current segment
        if (++cur == (**node).end()) {
            // End of segment reached: move to the next segment
            ++node;
            // Set the local iterator (cur) to the beginning of the new segment
            // or 0 (a null pointer equivalent for iterators) if it's the end.
            cur = *node ? (**node).begin() : 0;
        }
        return *this;
    }
    // ... other iterator operations like operator==, operator!=, etc.
};

template <class Iter, class T>
inline void fill (Iter first, Iter last, const T& x)
{
    typedef segmented_iterator_traits<Iter> Traits;
    
    // Dispatch call: The compiler chooses the segmented or nonsegmented helper 
    // based on the type of Traits::is_segmented_iterator (true_type or false_type).
    fill(first, last, x,
         typename Traits::is_segmented_iterator()); 
}

template <class SegIter, class T>
void fill (SegIter first, SegIter last, const T& x, true_type)
{
    typedef segmented_iterator_traits<SegIter> traits;

    // Decompose the full segmented iterators into segment and local iterators
    typename traits::segment_iterator sfirst = traits::segment (first);
    typename traits::segment_iterator slast = traits::segment (last);

    if (sfirst == slast) {
        // Case 1: Start and end are in the same segment. Use one local fill.
        fill(traits::local (first), traits::local (last), x);
    } else {
        // Case 2: Fill the *rest* of the starting segment
        fill(traits::local (first), traits::end(sfirst), x);

        // Case 3: Loop through all *middle* segments, filling them completely
        for (++sfirst; sfirst != slast; ++sfirst)
            fill(traits::begin(sfirst), traits::end(sfirst), x);

        // Case 4: Fill the *start* of the ending segment (sfirst now equals slast)
        fill(traits::begin(sfirst), traits::local (last), x);
    }
} 

Take away:

  • Use the most precise API for what you're trying to achieve.
    • insert_range instead of insert in a loop. (O(n) vs. N * log(M) )
    • use sorted_unique if the inputs are already sorted.
  • Use library facilities (e.g. views::zip) to benefit from concept-based optimizations.


4)

compiler flag
-Xclang -verify used for static_asserts
e.g.
clang++ -Xclang -verify -fsyntax-only test_integer_only.cpp

#include <type_traits>

template <typename T>
void strict_int_check() {
    static_assert(std::is_integral<T>::value, "Type must be an integer!");
}

void test() {
    // This line is valid, so no comment needed.
    strict_int_check<int>();

    // We WANT this line to fail. 
    // Without -verify, this breaks the build.
    // With -verify, Clang checks if the error matches the regex in {{...}}.
    strict_int_check<double>(); // expected-error {{Type must be an integer!}}
}

Dec 14, 2025

[C++][Concept] Techniques - 2

Reference:

Algorithm function objects
An algorithm function object (AFO), informally known as niebloid, is a customization point object (CPO)(not necessarily be but for now it is.) that is specified as one or more overloaded function templates.The name of these function templates designates the corresponding algorithm function object.



customization point object (CPO)
Reference:
[C++11][note] C++11 Library Design , initially where the `niebloid` concept comes from when Eric Niebler given this talk; using functor to avoid ADL.



All customization point objects of the same class type are equal. The effects of invoking different instances of that type on the same arguments are equivalent, whether the expression denoting the instance is an lvalue or rvalue, const-qualified or not. However, a volatile-qualified instance is not required to be invocable. Thus, customization point objects can be copied freely and the copies can be used interchangeably.

Let Fn be the type of a customization point object, and Args... be a set of types, if std::declval<Args>()... meet the requirements for arguments to Fn, Fn models:
std::invocable<Fn, Args...>,
std::invocable<const Fn, Args...>,
std::invocable<Fn&, Args...>, and
std::invocable<const Fn&, Args...>.
Otherwise, no function call operator of Fn participates in overload resolution.


CPO:
a customization point object is a semiregular, function object (by definition) that exists to handle constrained ADL dispatch for you. ranges::begin, ranges::swap, etc, are customization point objects.


niebloid:
a niebloid is a colloquial name for the algorithms declared in std::ranges that inhibit ADL. The only implementation strategy in standard C++20 for them is to make them function objects, but they are not required to be objects at all (nor are they required to be copyable, etc.) and a possible future language extension would allow them to be implemented as proper function templates. (e.g. [[no_adl]])



concepts:

The semiregular concept specifies that a type is both copyable and default constructible. It is satisfied by types that behave similarly to built-in types like int, except that they need not support comparison with ==.

The regular concept specifies that a type is regular, that is, it is copyable, default constructible, and equality comparable.
It is satisfied by types that behave similarly to built-in types like int, and that are comparable with ==.



Equality preservation
Equality preservation in C++ concepts means that if you give an expression equal inputs,
it must produce equal outputs (the result of the expression and any modified operands).

Dec 10, 2025

[C++] container.assign_range(rg)

Reference:
https://eel.is/c++draft/sequence.reqmts#64

a.assign_range(rg)
Recommended practice:
If R models ranges​::​approximately_sized_range and ranges​::​distance(rg) <= ranges​::​reserve_hint(rg) is true, an implementation should not perform any reallocation.

Dec 7, 2025

[C++][Data structure] concurrent skip list.

Here SkipListNode initialize the size to size of SkipListNode + hight of the skiplist node.
https://github.com/facebook/folly/blob/d2e6fe65dfd6b30a9d504d0409ac733cbaa73125/folly/ConcurrentSkipList-inl.h#L66

size_t size =
	sizeof(SkipListNode) + height * sizeof(std::atomic<SkipListNode*>);

This is the clever trick used in C, while SkipListNode has the last data member declared of size 0 array. i.e. if the hight of the SkipListNode is 5, the size of SkipListNode is
SkipListNode + 5 * sizeof(std::atomic),
thus we could access skip_[4] without out-of-idx-bound.
std::atomic<SkipListNode*> skip_[0];
https://github.com/facebook/folly/blob/d2e6fe65dfd6b30a9d504d0409ac733cbaa73125/folly/ConcurrentSkipList-inl.h#L173

Hense std::atomic skip_[0] is malloc not through constructor, when SkipListNode is destructed, should call `skip_[i].~atomic()` manually. (i.e. destruct atomic before its memory being deallocated.)
~SkipListNode() {
	for (uint8_t i = 0; i < height_; ++i) {
      skip_[i].~atomic();
    }
}

Nov 25, 2025

[C++] lambda restrictions

Lambda Restrictions (as of c++26)

  • No implicit conversions, e.g. base class slicing
  • No member typedef
  • No compiler-selected overloading
  • No user-defined conversion functions
  • No user-defined implicit copy/move operations
  • No user-defined destructors
  • Limited template argument deduction
  • No directly extendable overload sets
  • No operator overloading
  • No array captures (at runtime. For compile time known size array, just like defined in struct, they are verbatimly copied/captured)
  • No separating declaration and definition
  • No linking across TUs (a lambda's type is effectively "private" to the file it is written in.)