2025-11-16T02:49:12.114465

Concept-Based Generic Programming in C++

Stroustrup
We present programming techniques to illustrate the facilities and principles of C++ generic programming using concepts. Concepts are C++'s way to express constraints on generic code. As an initial example, we provide a simple type system that eliminates narrowing conversions and provides range checking without unnecessary notational or run-time overheads. Concepts are used throughout to provide user-defined extensions to the type system. The aim is to show their utility and the fundamental ideas behind them, rather than to provide a detailed or complete explanation of C++'s language support for generic programming or the extensive support provided by the standard library. Generic programming is an integral part of C++, rather than an isolated sub-language. In particular, key facilities support general programming as well as generic programming (e.g., uniform notation for types, lambdas, variadic templates, and C++26 static reflection). Finally, we give design rationales and origins for key parts of the concept design, including use patterns, the relationship to Object-Oriented Programming, value arguments, notation, concept type-matching, and definition checking.
academic

Concept-Based Generic Programming in C++

基本信息

  • 论文ID: 2510.08969
  • 标题: Concept-Based Generic Programming in C++
  • 作者: Bjarne Stroustrup (Columbia University)
  • 分类: cs.PL cs.SE
  • 发表时间: 2025年
  • 论文链接: https://arxiv.org/abs/2510.08969

摘要

本文展示了使用概念(concepts)来说明C++泛型编程设施和原理的编程技术。概念是C++表达泛型代码约束的方式。作为初始示例,本文提供了一个简单的类型系统,该系统消除了窄化转换并提供范围检查,而无需不必要的符号或运行时开销。概念被广泛用于提供用户定义的类型系统扩展。本文旨在展示概念的实用性和背后的基本思想,而不是提供C++泛型编程语言支持或标准库广泛支持的详细或完整解释。泛型编程是C++的组成部分,而不是一个孤立的子语言。最后,本文给出了概念设计关键部分的设计理念和起源,包括使用模式、与面向对象编程的关系、值参数、符号表示、概念类型匹配和定义检查。

研究背景与动机

问题背景

  1. 泛型编程的挑战:传统的C++泛型编程缺乏明确的接口规范,导致编译时错误信息晦涩难懂,程序员和编译器都难以理解和使用模板接口。
  2. 类型安全问题:C++继承了C语言的隐式类型转换规则,特别是算术类型间的窄化转换(如从大整型到小整型的转换可能丢失信息),这是错误和安全问题的重要来源。
  3. 范围检查缺失:传统的指针和数组使用容易导致缓冲区溢出等安全问题,缺乏有效的范围检查机制。

研究动机

本文的核心动机是展示如何使用C++20引入的概念(concepts)来:

  • 提供静态类型安全的接口规范
  • 实现零开销的类型安全
  • 构建用户定义的类型系统扩展
  • 保持泛型编程与通用编程的统一性

设计目标

论文遵循Alex Stepanov提出的泛型编程目标:"最通用、最高效、最灵活的概念表示",并满足以下设计要求:

  • 通用性:必须能够表达超出想象的更多内容
  • 无妥协的效率:泛型代码相比等效的低级代码不应产生运行时开销
  • 静态类型安全接口:类型系统必须足够灵活,允许编译时检查不依赖运行时值的接口的大多数方面

核心贡献

  1. 提出了基于概念的类型安全编程技术:展示了如何使用概念来消除窄化转换和提供范围检查,同时保持零运行时开销。
  2. 构建了实用的类型系统扩展
    • 实现了Number<T>类型,消除危险的窄化转换
    • 设计了安全的Span类型,提供范围检查的数组访问
    • 展示了概念在算法设计中的应用
  3. 提供了概念设计的深入理念:详细阐述了概念作为编译时函数的设计决策、与面向对象编程的关系、符号表示选择等关键设计考虑。
  4. 展示了泛型编程的统一性:证明了泛型编程是C++的组成部分,而不是孤立的子语言,与其他语言特性(如lambda、变参模板、静态反射)无缝集成。

方法详解

概念的基本定义

概念是编译时求值的函数(谓词),可以接受类型参数:

template<typename T>
concept Num = integral<T> || floating_point<T>;

消除窄化转换的方法

检测窄化的概念定义

template<typename T, typename U>
concept Can_narrow_to = 
    ! same_as<T, U>    // 不同类型
    && Num<T> && Num<U>   // 都是数值类型
    && ( 
        (floating_point<T> && integral<U>) // 可能丢失小数部分
        || (numeric_limits<T>::digits > numeric_limits<U>::digits) // 可能截断
        || (signed_integral<T>!=signed_integral<U> && sizeof(T)==sizeof(U)) // 可能改变符号
    );

运行时检查函数

template<Num U, Num T>
constexpr bool will_narrow(U u) {
    if constexpr (!Can_narrow_to<T, U>)
        return false;  
    // 只在可能窄化时进行运行时检查
    T t = u;
    return (t != u);
}

Number类型的实现

template<Num T>
class Number {
    T val;
public:
    template<Num U>
    constexpr Number(const U u) : val{convert_to<T>(u)} { }
    
    operator T() { return val; }
};

安全范围访问的实现

Span类型设计

template<class T>
class Span {
    T* p;
    unsigned n;
public:
    Span(Spanable auto& s) : p{data(s)}, n{size(s)} {}
    
    T& operator[](Number<unsigned> i) { 
        return p[check(i)]; 
    }
    
private:
    unsigned check(unsigned nn) {
        if (n <= nn) throw Span_range_error{};
        return nn;
    }
};

概念在算法中的应用

使用概念约束的sort函数

template<typename R, typename Pred = ranges::less>
concept Sortable_range = 
    ranges::random_access_range<R>
    && sortable<ranges::iterator_t<R>, Pred>;

template<typename R, typename Pred = ranges::less>
requires Sortable_range<R,Pred>
void sort(R& r, Pred p = {}) {
    sort(r.begin(), r.end(), p);
}

技术创新点

1. 使用模式(Use Patterns)

概念通过使用模式来定义要求,而不是通过类似类定义的函数集合:

template<typename T, typename U = T>
concept equality_comparable = requires(T a, U b) {
    {a==b} -> Boolean;
    {a!=b} -> Boolean;
    {b==a} -> Boolean;
    {b!=a} -> Boolean;
}

这种方法的优势:

  • 处理混合模式算术
  • 处理隐式转换
  • 提供接口稳定性

2. 概念作为函数

概念被设计为编译时函数,而不是类型的类型或类型的属性:

  • 可以询问类型的属性("你是迭代器吗?")
  • 可以接受多个参数
  • 在编译时执行,效率很高

3. 零开销抽象

通过编译时检查和条件编译,实现零运行时开销:

template<Num U, Num T>
constexpr bool will_narrow(U u) {
    if constexpr (!Can_narrow_to<T, U>)
        return false;  // 编译时确定,无运行时成本
    // 只在必要时进行运行时检查
}

实验设置

代码示例验证

论文通过大量实际代码示例来验证方法的有效性:

  1. 算术转换示例:展示Number类型如何防止窄化转换
  2. 范围访问示例:展示Span类型如何提供安全的数组访问
  3. 排序算法示例:展示概念如何改进算法接口的清晰度和安全性

性能考虑

  • 编译时检查不产生运行时开销
  • 只在潜在窄化情况下进行运行时检查
  • 保持与底层代码相同的性能特征

实验结果

主要成果

  1. 类型安全提升
    Number<int> test() {
        Number<unsigned int> ii = 0;
        ii = 2;   // OK
        ii = -2;  // throws - 捕获了符号错误
    }
    
  2. 范围安全
    Span<int> sa{array};
    int x = sa[10];   // OK if in range
    int y = sa[-1];   // throws - 捕获了负索引错误
    
  3. 算法接口改进
    sort(vec);  // 简洁的接口
    sort(lst);  // 编译时错误:list不支持随机访问
    

性能特征

  • 编译时检查:零运行时开销
  • 运行时检查:仅在必要时进行,开销最小
  • 优化友好:支持内联和其他编译器优化

相关工作

泛型编程历史

  • STL概念:迭代器、序列、容器(1990年代早期)
  • 数学概念:单子、群、环、域(数百年历史)
  • 图论概念:边、顶点、图、DAG(1736年起)

C++概念发展

  • 2003年:首次概念提案
  • 2009年:C++0x概念被移除
  • 2017年:概念重新引入
  • 2020年:C++20正式包含概念

与其他约束系统的比较

论文指出许多约束系统依赖于函数集合(类似类定义),而C++概念使用函数式方法,提供更大的灵活性。

结论与讨论

主要结论

  1. 概念是编译时函数:这种设计提供了灵活性和效率的最佳平衡。
  2. 泛型编程与OOP互补
    • GP专注于函数(算法)和参数要求
    • OOP专注于对象和接口实现
    • 两者可以有效结合使用
  3. 统一的编程模型:泛型编程不是孤立的子语言,而是C++的组成部分。

设计理念

概念vs类层次结构

  • 概念:指定模板必须能够对其参数执行的操作
  • 类层次:预定义接口,需要预见性
  • 概念更灵活:可以使用任何满足要求的类型组合

符号表示选择

// 理想的函数式语法(未采用)
void sort(Sortable_range& r);

// 实际采用的妥协语法
void sort(Sortable_range auto& r);

未来发展方向

  1. 公理(Axioms):为分析器和代码生成器指定构造的语义属性
  2. 输出范围:增加范围安全性,对输出操作进行范围检查
  3. 符号表示改进:消除概念后的auto冗余
  4. 类的重载:基于概念允许类重载
  5. 模式匹配:函数式编程风格的模式匹配
  6. 统一函数调用:统一函数式和OOP式调用语法

深度评价

优点

  1. 实用性强:提供了可直接应用的编程技术,解决了实际的类型安全问题。
  2. 理论与实践结合:既有深厚的理论基础(数学概念),又有具体的实现示例。
  3. 零开销设计:通过巧妙的编译时检查设计,实现了类型安全而无性能损失。
  4. 全面的设计考虑:涵盖了从基本语法到高级特性(如静态反射)的完整设计空间。
  5. 权威性:作者Bjarne Stroustrup是C++语言的创造者,具有无可置疑的权威性。

不足

  1. 学习曲线陡峭:概念的语法和使用模式对初学者来说可能较为复杂。
  2. 编译时间影响:大量的编译时检查可能增加编译时间,论文对此讨论不够充分。
  3. 向后兼容性挑战:虽然保持了兼容性,但新旧代码混合使用时可能产生困惑。
  4. 错误消息质量:虽然概念改善了错误消息,但复杂概念的错误消息仍可能难以理解。

影响力

  1. 对C++社区的影响:为C++20概念的实际应用提供了权威指导。
  2. 对其他语言的启发:概念设计理念可能影响其他语言的约束系统设计。
  3. 教育价值:为泛型编程教学提供了优秀的教材。
  4. 实用价值:提供的技术可直接用于提高代码的类型安全性。

适用场景

  1. 系统级编程:需要高性能和类型安全的底层代码。
  2. 库设计:为泛型库提供清晰的接口规范。
  3. 教学和培训:作为泛型编程概念和技术的教学材料。
  4. 代码重构:将现有的不安全代码重构为类型安全的版本。

参考文献

论文包含了丰富的参考文献,涵盖了从C++语言设计到泛型编程理论的各个方面,主要包括:

  • Alex Stepanov关于STL和泛型编程的开创性工作
  • C++标准委员会的技术报告和提案
  • 作者本人关于C++设计和演进的历史文档
  • 相关的编程语言理论研究

总结:这是一篇具有重要理论和实践价值的论文,由C++语言创造者亲自撰写,全面展示了概念在C++泛型编程中的应用。论文不仅提供了实用的编程技术,还深入阐述了设计理念,对C++程序员和编程语言研究者都具有重要参考价值。