前提

Item26 已证明:通用引用 + 普通重载 = 必出问题(劫持调用、编译报错、破坏拷贝 / 移动构造、继承失效)。

本条款给出5 种合法替代方案,从简单到高级,覆盖所有场景。

5 种通用引用重载的替代方案

1. 放弃重载,直接改函数名

  • 做法:把重载函数改成不同名字(如logAndAddName/logAndAddNameIdx)
  • 优点:最简单,无任何匹配风险
  • 缺点:构造函数不能用(名字固定为类名),失去重载的语法便利

2. 退而求其次:传 const T&

  • 做法:放弃通用引用,用 C++98 的常量左值引用
  • 优点:代码简单、匹配稳定、无坑
  • 缺点:效率低,无法利用移动语义、做不到完美转发

3. 按值传递(pass by value)

  • 做法:形参直接传值,配合std::move初始化成员
1
explicit Person(std::string n) : name(std::move(n)) {}
  • 优点:无匹配冲突,代码简洁,兼顾拷贝 / 移动
  • 缺点:比完美转发略低效(多一次移动 / 拷贝)
  • 适用:构造函数等对效率要求没极致的场景

4. 标签分派(tag dispatch)

  • 对外只留一个通用引用函数,内部靠编译期标签分发给重载实现

  • 做法:

    1. 外层:通用引用函数,用is_integral/remove_reference生成true_type/false_type标签
    2. 内层:重载impl函数,靠标签类型决定走哪个逻辑
  • 优点:保留完美转发 + 重载,完全避免匹配冲突

  • 缺点:代码分层,稍复杂

5. std::enable_if 约束模板(最强大、最通用)

  • 用编译期条件限制通用引用模板的启用范围,让它只匹配你想让它匹配的类型

  • 搭配:

    • std::decay_t<T>:去掉引用、const/volatile
    • std::is_base_of:排除自身 + 派生类(解决拷贝 / 移动构造被劫持)
    • std::is_integral:排除整型(走普通 int 重载)
  • 优点:完美转发 + 重载共存,效率最高、最灵活

  • 缺点:语法复杂(C++14 别名模板简化写法)

最终折中选择

  • 追求极致效率 + 完美转发:用 tag dispatch / std::enable_if

    优点:无临时对象、性能最优

    缺点:编译报错晦涩(可加static_assert优化提示)

  • 追求简单稳定 + 易读:用 const T& / 按值传递

    优点:报错清晰、代码好写

    缺点:效率略低

总结

  1. 通用引用不能直接重载,替代方案:改名、const T&、传值、标签分派、enable_if 约束
  2. std::enable_if 是唯一能让通用引用 + 重载合法共存的方法,靠条件限制匹配范围
  3. 高效(完美转发)和易用(简单方案)需要根据场景折中选择