2.7. 未来之路

我们即将探讨的最后一个关于代码是否符合标准的情形,涉及 C++ 生态系统中最基础、最核心的部分:编译器本身。

请不要忘记,编译器本质上也是一种程序,由数百万行代码组成。全球各地都有开发者在为它们贡献代码:添加新特性、修复漏洞、提升标准兼容性、发布新版本 —— 这一切都是为了确保编译器能够正常运行。

这些项目也有自己的开发时间线。某些特性的实现不可能一蹴而就,有时某个特定时间点上,某款编译器尚未支持某些标准特性,仅仅是因为没有足够的人力或优先级去完成相关实现。

在 C++ 知识的权威来源https://en.cppreference.com/w/cpp/compiler_support(即 C++ 官方网站或相关权威资源)中,有一份非常实用的文档,详细列出了各个 C++ 标准特性的支持情况,以及哪些编译器已经实现了某个特定功能。

在标准更新期间(或者当你被迫使用一个尚未实现某些特性的旧版编译器时),C++ 开发者社区曾采用过许多技巧和变通方案,以弥补即将到来的新版本编译器中缺失的功能。

例如,当 mutable 关键字在 C++98 中引入时,一些编译器对它的支持滞后于其他主流编译器。对于使用这些编译器的开发者来说,在 const 成员函数中修改成员变量(这是在同一标准中引入的功能)变得颇具挑战性。

这种情况下,开发者不得不使用一种(相当丑陋的)变通方式来模拟 mutable 的行为:

class Counter {
  int viewCount = 0;
public:
  void view() const {
    const_cast<Counter*>(this)->viewCount++;
  }
  void print() const {
    std::cout << "Count: " << viewCount << std::endl;
  }
}

假设编译器支持 const_cast,那么这段代码是合法的。但如果连 const_cast 都不被支持,那就只能退而求其次,使用传统的 C 风格强制类型转换:

((Counter*)(this))->viewCount++;

mutable 并不是第一个因编译器缺乏支持而给开发者带来困扰的语言特性。早在 C++11 引入 constexpr 之前(甚至在其推出多年后,对于 Microsoft Visual C++ 的开发者来说也是如此),想要实现编译时常量表达式,必须依赖各种模板元编程技巧(或者直接使用宏,但宏是“邪恶”的,应尽量避免使用)。

例如,下面是一个典型的模板递归实现,用于计算阶乘:

template <unsigned int N>
struct Factorial {
  static const unsigned long long value = N * Factorial<N - 1>::value;
};
template <>
struct Factorial<0> {
  static const unsigned long long value = 1;
};
const unsigned long long fac5 = Factorial<5>::value;

相比之下,使用现代支持 constexpr 的编译器,可以写出更加简洁、直观的等效实现:

constexpr unsigned long long factorial(unsigned int n) {
  return n <= 1 ? 1 : n * factorial(n - 1);
}
const unsigned long long fac5too = factorial(5);

我觉得,这种写法不仅在语法上更清晰,而且在可读性和表达力方面实现了质的飞跃。。