T240725 说到,Unnamed namespaces 有一个唯一的名称会通过 using-directives 自动导入,倘若可以手动指定这个名称,就是 inline namespaces 了。此时,自动导入的名称就是 inline namespace 的名称,比如 std::literals 和 std::liternals::chrono_literals 的实现: #if __cplusplus >= 202002L inline namespace literals { inline namespace chrono_literals { /// @addtogroup chrono /// @{ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wliteral-suffix" /// Literal suffix for creating chrono::day objects. ///… Continue Reading T240730 inline namespace

Unnamed namespaces,也叫 Anonymous namespaces,是命名空间的一种特殊形式。这种形式可以省略命名空间的名称,如: namespace { /* .. . */ } 在语义上与等价于: namespace unique_name { /* … */ } using namespace unique_name; 编译器会自动生成一个唯一的名称,并使用 using-directives 自动导入名称。 与其他形式的命名空间不同,Unnamed namespaces 的链接方式是 Internal Linkage,标准描述为: An unnamed namespace or a namespace declared directly or indirectly within an unnamed namespace has internal linkage. All other namespaces… Continue Reading T240725 Unnamed namespaces

本篇分享详细解释一下 Top-level const 和 Low-level const 的概念。 Top-level 和 Low-level 指的是名称修饰符所处的位置,const 是最常见的修饰符,举一反三,便可知所有情况。 由例子切入理解: int a = 42; int b = 10; int* const p1 = &a; // top-level const *p1 = 22; // Ok p1 = &b; // Error! int const* p2 = &a; // low-level const *p2 = 22;… Continue Reading T240516 Top-level const and Low-level const

这是昨天群里的一个问题,但我想谈的是背后的本质问题。 这个问题其实可以简化为: template <typename …Args> void foo(std::function<bool(Args…)>) { } int main() { // error: no matching function for call to 'foo(main()::<lambda(int)>)' foo([](int) { return true; }); } 本质原因在 TAD(模板参数推导),这里实参 A = closure type,形参 P = std::function<bool(Args…)>。由于类型完全不同,P 无法替换成 A,这里 TAD 完全不可能推断出 Args… 的类型。于是,模板函数因模板替换失败,而被早早移除,连一级筛选 Candidate functions 都未能进入。 不用 std::function 可能更易理解。一个例子: template <class… Continue Reading T240104 Failed to Passing a Lambda Expression to std::function

不知大家是否遇到过这样的代码: template <class… Args> void f(Args……) {} 参数包后面紧跟着 6 个 .,一般不是用 … 来扩展参数包吗?这是什么鬼东西? 这个其实可以称为 Two ellipsis operators(非正式称谓),虽说极其罕见,但却是 C++11 标准的一部分,见 [dcl.fct]/3: parameter-declaration-clause:  parameter-declaration-listopt …opt  parameter-declaration-list , … 也就是说,它其实是 …, … 的省略写法。以下这三种写法完全等价: template <class… Args> void f(Args……) {} template <class… Args> void f(Args… …) {} template <class… Args> void f(Args…, …) {}… Continue Reading T240102 What does the …… mean in C++?

模板加约束,其中有迷思,着意分析一下。 template <typename T> void f(T t) {} template <typename T> void f(T t) requires true {} f(1); // the second one wins 二比一特殊,决战胜出,似乎全在预料之内。 根据 [temp.over.link]/7: Two function templates are equivalent if they are declared in the same scope, have the same name, have equivalent template-heads, and have return types,… Continue Reading T231212 Dummy Rquires Clause Positioning in Function Template Overloading

上篇讲解了 Compile time for,它是遍历技术中的迭代方式之一,特别常用。 它一般与 Fold expressions 组合使用,它们两个,一个相当于编译期的 Range-based for,一个相当于编译期的 for loop。由于 std::index_sequence<N> 本身只是一个静态序列,并不具备向前迭代的能力,而 Fold expressions 天生就是干这个的,两相组合,Compile time for 的功能便可完全具备。 再者,Fold expressions 只适合用来局部访问数据,不支持全局操作,当你想要整体性操作某一数据集合时,除了递归,这种迭代方式是唯一选择。相较起来,其他方式都要更加麻烦,参考上篇的例子。 考虑之前群内讨论过的一个例子,求结构体成员个数,来对比一下几种常见的编译期遍历技术。 下面是一种递归解法: struct Any { template <class T> operator T() const; }; template <class T, class… Args> requires std::is_aggregate_v<T> consteval auto CountAggregateMembers() { if constexpr (requires {… Continue Reading T231119 几种遍历技术实现结构体成员计数的比较

先说明,本篇并不是说 Expansion statements。 遍历方式分为两种,自下而上叫迭代,自上而下叫递归。 递归往往需要多个函数(if constexpr 可以避免),一般迭代是一种更加自然的方式。前面正式文章写过大量 tuple-like 类型的遍历方法,虽然也包含了非 tuple-like 类型的迭代方法,但却是作为次要部分出现的,这篇单独摘出来再讲解一下。 tuple-like 类型就是指存在 std::tuple_size<T> 和 std::get<I>(t) 定义的类型,此类类型具备通用的遍历方式。由旧文可知,目前最简单的迭代方式是借助 std::apply: void pack_iterator_with_tuple(auto&&… args) { auto tuple = std::make_tuple(std::forward<decltype(args)>(args)…); std::apply([](const auto&… tupleArgs) { ((std::cout << tupleArgs << " "), …); }, tuple); } 当然,对于参数包来说,最简单的方式是借助 Fold expressions。 void pack_iterator_with_fold_expr(auto&&… args) { ((std::cout << std::forward<decltype(args)>(args)… Continue Reading T231111 Compile time for

这篇稍微总结一下异构类型的迭代方法。 首先是静态异构类型,主要指的就是 tuple like 类型,它们可以使用 std::apply 迭代。 void print(auto tuple_like_value) { std::apply([](const auto&… args) { (fmt::print("{} ", args), …); }, tuple_like_value); fmt::print("\n"); } int main() { // Iterate std::pair { using heterogenous_t = std::pair<int, double>; heterogenous_t container { 1, 0.8 }; print(container); } // Iterate std::tuple { using heterogenous_t =… Continue Reading T231104 异构类型迭代法

宏编程的基本原理都已暗含于解决前两节问题的过程当中,本节开始,依此继续展开。 经由前面的努力,我们已掌握条件逻辑在宏编程的表示,代码生成往往涉及循环,所以今天来讲如何实现一个 FOR_EACH。 FOR_EACH 用来迭代数据集合,依次取出数据,然后调用函数处理。因此,参数很容易确定,一个回调函数加上可变参数。代码表示: #define FOR_EACH(call, …) 至于展开参数,可以利用重载技术加上递归思想,因为重载技术在前文已经有相关组件,所以实现起来非常简单。 #define _FOR_EACH_1(call, x) call(x) #define _FOR_EACH_2(call, x, …) call(x) _FOR_EACH_1(call, __VA_ARGS__) #define _FOR_EACH_3(call, x, …) call(x) _FOR_EACH_2(call, __VA_ARGS__) #define _FOR_EACH_4(call, x, …) call(x) _FOR_EACH_3(call, __VA_ARGS__) #define _FOR_EACH_5(call, x, …) call(x) _FOR_EACH_4(call, __VA_ARGS__) #define FOR_EACH(call, …) \ OVERLOAD_HELPER(_FOR_EACH_, COUNT_VARARGS(__VA_ARGS__))(call, __VA_ARGS__) 通过层层递归,便可以把大问题拆分成一个个小问题。如果数据集合大小为 5,那么就从… Continue Reading T230925 Generative Metaprogramming with Macro Preprocessor (Part 3)