Skip to content

C++ 小众特性之"用户自定义类模板实参推导指引"

约 780 字大约 3 分钟

技术随笔cpp

2026-01-18

用户自定义类模板实参推导指引(User-defined deduction guides)是 C++17 引入的 类模板实参推导(CTAD, Class Template Argument Deduction)特性的重要组成部分。

语法形式

其语法形式与带有尾随返回类型的函数声明很相近,除了以下区别:

  • 函数名必须是类模板的名称
  • 使用 -> 指定推导出的模板实例化类型
  • 不需要实现函数体

基本语法结构:

template <typename... >
ClassName(Args list) -> ClassName<Template Args list>;

作用与意义

正如其名,这种语法的主要作用是为类模板的构造函数提供 自定义的类型推导规则, 使编译器在从构造函数参数推导模板参数时,能够按照我们期望的方式进行推导。

这在我们使用类模板是,如果需要尾置强制执行的默认参数;亦或者需要对类模板的构造进行 更复杂的类型推导逻辑时,尤为有用。

小例子:内联日志打印器

下面展示一个利用推导指引配合模板和编译器属性实现的零开销日志打印工具:

#include <format>
#include <iostream>
#include <source_location>

template <typename... Args>
class Logger {
 public:
  // 使用 always_inline 属性确保零运行时开销
  [[gnu::always_inline]] explicit Logger(
      std::format_string<Args...> fmt,
      Args&&... args,
      std::source_location loc = std::source_location::current()) { // 打印函数名
    std::println(std::cerr, "[LOG] {}; with stack {}",
                 std::format(fmt, std:: forward<Args>(args)...),
                 loc.function_name());
  }
};

// 用户自定义推导指引:从构造函数参数推导模板参数
// 这里如果不使用 `用户自定义推导指引`,则无法自动推导 Args...,并且需要显式调用 std::source_location::current()
template <typename... Args>
Logger(std:: format_string<Args...> fmt, Args&&... args) -> Logger<Args...>;

void foo() {
  // 无需显式指定模板参数,编译器根据推导指引自动推导, 使用起来与常规的 std::format 类似
  Logger("Hello {}, the answer is {}", "world", 42);
  // 输出:  "[LOG] Hello world, the answer is 42; with stack foo"
}

关键点说明:

  • std::format_string<Args...> 确保编译期格式字符串检查, 这里的类模板参数 Args... 也需要被推导出来
  • std::source_location 自动捕获调用位置信息
  • 推导指引使得可以直接写 Logger(...) 而无需 Logger<const char*, int>(...)

使用限制

用户自定义推导指引有一个重要限制:

参数列表不能使用简化函数模板语法(Abbreviated Function Template Syntax)中的占位符类型

即不允许使用 autoConcept auto 作为参数类型:

// 错误例:推导指引不支持 auto 参数
template <typename T>
ClassName(auto fmt, T arg) -> ClassName<T>;

// 正确写法:必须显式声明模板参数
template <typename Fmt, typename T, typename... Args>
ClassName(Fmt fmt, T arg, Args&&...) -> ClassName<T, Args...>;

这是因为推导指引本身就是用于指导类型推导的机制,使用 auto 会引入二义性和循环推导问题。