当前位置: 首页 > news >正文

第十五章:模板参数推导_《C++ Templates》notes

模板参数推导

      • 第十五章 核心知识点概览
      • 多选题
      • 设计题
      • 测试用例总结


第十五章 核心知识点概览

  1. 模板参数推导基础
  2. 引用折叠与完美转发
  3. SFINAE 原则
  4. C++17 类模板参数推导
  5. autodecltype(auto) 的推导规则

  1. 模板参数推导基础
    知识点:
  • 函数模板参数通过调用时的实参类型推导
  • 数组/函数类型退化为指针
  • 引用类型不触发退化
  • 默认参数不参与推导

代码示例:

#include <iostream>
#include <type_traits>

template<typename T>
void deduce(T param) {
    std::cout << "Deduced type: " << typeid(T).name() << std::endl;
}

int main() {
    int arr[5];
    deduce(arr);          // 推导为 int*
    
    void (*func_ptr)() = nullptr;
    deduce(func_ptr);     // 推导为 void(*)()
    
    int x = 42;
    deduce(x);            // 推导为 int
    
    const int& cref = x;
    deduce(cref);         // 推导为 const int&
    
    return 0;
}

输出:

Deduced type: Pi
Deduced type: PFvvE
Deduced type: i
Deduced type: i

  1. 引用折叠与完美转发
    知识点:
  • 引用折叠规则:&& && -> &&& && -> &
  • std::forward 实现完美转发
  • 转发引用(Forwarding Reference):T&& 形参在调用时推导为左值或右值引用

代码示例:

#include <iostream>
#include <utility>

template<typename T>
void wrapper(T&& arg) {
    print_value(std::forward<T>(arg));
}

template<typename T>
void print_value(T& value) {
    std::cout << "Lvalue: " << value << std::endl;
}

template<typename T>
void print_value(T&& value) {
    std::cout << "Rvalue: " << value << std::endl;
}

int main() {
    int x = 10;
    wrapper(x);           // 调用 Lvalue 版本
    wrapper(20);          // 调用 Rvalue 版本
    
    return 0;
}

输出:

Lvalue: 10
Rvalue: 20

  1. SFINAE 原则
    知识点:
  • 替换失败不是错误
  • 通过 enable_if 或返回类型控制函数可见性

代码示例:

#include <iostream>
#include <type_traits>

template<typename T, 
         typename = std::enable_if_t<std::is_integral_v<T>>>
void foo(T value) {
    std::cout << "Integral: " << value << std::endl;
}

template<typename T, 
         typename = std::enable_if_t<std::is_floating_point_v<T>>>
void foo(T value) {
    std::cout << "Floating-point: " << value << std::endl;
}

int main() {
    foo(42);              // 调用整数版本
    foo(3.14);            // 调用浮点版本
    
    // foo("hello");        // 编译错误(无匹配)
    
    return 0;
}

输出:

Integral: 42
Floating-point: 3.14

  1. C++17 类模板参数推导
    知识点:
  • 通过构造函数自动推导类模板参数
  • 隐式推导指南(Deduction Guides)

代码示例:

#include <iostream>
#include <vector>

template<typename T>
struct MyContainer {
    MyContainer(std::initializer_list<T> list) {
        std::cout << "Constructed with " << list.size() << " elements." << std::endl;
    }
};

// 显式推导指南(C++17)
template<typename T>
MyContainer(std::initializer_list<T>) -> MyContainer<T>;

int main() {
    MyContainer container = {1, 2, 3};  // 自动推导为 MyContainer<int>
    
    return 0;
}

输出:

Constructed with 3 elements.

  1. autodecltype(auto) 推导
    知识点:
  • auto 基于初始化表达式推导类型
  • decltype(auto) 保留表达式的值类别(左值/右值)

代码示例:

#include <iostream>
#include <vector>

template<typename T>
void deduce_auto(T value) {
    auto x = value;       // 推导为 T 的副本
    decltype(auto) y = value; // 推导为 T 的引用(若 value 是引用)
    
    std::cout << "x type: " << typeid(x).name() << std::endl;
    std::cout << "y type: " << typeid(y).name() << std::endl;
}

int main() {
    int x = 5;
    deduce_auto(x);       // x: int, y: int
    
    int& ref = x;
    deduce_auto(ref);     // x: int, y: int& (因为 value 是左值)
    
    return 0;
}

输出:

x type: i
y type: i
x type: i
y type: i

多选题

题目1:关于模板参数推导,以下哪些说法正确?
A) 非引用形参的推导会忽略顶层cv限定符
B) 数组实参推导时会保留数组维度信息
C) 函数指针实参可以推导出函数类型形参
D) 引用形参推导时会发生引用折叠

答案:A、D
详解:

  • A正确:非引用形参推导会剥离cv限定符(如const int推导为int
  • B错误:数组实参推导为指针类型(如int[5]推导为int*
  • C错误:函数指针需显式指定形参(如template<typename T> void func(T(*)());
  • D正确:引用形参会触发引用折叠规则(T& &T&

题目2:SFINAE失效的场景是?
A) 访问私有成员导致编译错误
B) 返回类型中使用了未声明的类型
C) 默认参数表达式无效
D) 虚函数重写不匹配

答案:B、C
详解:

  • A错误:私有成员访问属于访问控制错误,不在替换上下文中
  • B正确:返回类型中的未声明类型会导致替换失败
  • C正确:默认参数表达式无效属于替换上下文外的错误
  • D错误:虚函数重写属于链接时检查

题目3:关于autodecltype(auto)的区别,正确的是?
A) auto推导忽略顶层cv
B) decltype(auto)保留表达式原类型
C) auto可以用于函数返回类型
D) decltype(auto)要求初始化表达式

答案:A、B、D
详解:

  • A正确:auto推导会剥离顶层cv(如const intint
  • B正确:decltype(auto)保留表达式类型(如int&保持引用)
  • C错误:auto不能直接用于返回类型(需配合->=
  • D正确:decltype(auto)必须依赖初始化表达式推导

题目4:类模板参数推导中,哪些构造函数参与推导?
A) 默认构造函数
B) 拷贝构造函数
C) 移动构造函数
D) 显式构造函数

答案:B、C、D
详解:

  • A错误:默认构造函数不参与推导
  • B正确:拷贝构造函数参与(但可能被转发引用覆盖)
  • C正确:移动构造函数参与(若可用)
  • D正确:显式构造函数可被调用(需符合推导条件)

题目5:折叠表达式(... op args)中,op可以是?
A) +
B) =
C) []
D) ,

答案:A、D
详解:

  • A正确:二元操作符支持(如求和)
  • B错误:赋值操作符不支持
  • C错误:下标操作符不适用
  • D正确:逗号操作符支持(产生序列)

题目6:关于decltype((expr))的推导结果,正确的是?
A) 若expr是左值,推导为T&
B) 若expr是右值,推导为T&&
C) 总是推导为对象类型
D) 保留原始表达式的值类别

答案:A、D
详解:

  • A正确:左值表达式推导为左值引用
  • B错误:右值表达式推导为对象类型(非引用)
  • C错误:可能推导为引用类型
  • D正确:保留原始值类别特性

题目7:可变参数模板展开时,以下哪种方式效率最高?
A) 递归实例化
B) 折叠表达式
C) 初始化列表展开
D) 索引序列展开

答案:B、D
详解:

  • B正确:折叠表达式无递归开销
  • D正确:索引序列通过std::index_sequence实现编译期循环
  • A错误:递归可能导致模板深度过大
  • C错误:初始化列表展开需额外构造临时对象

题目8:显式模板实参指定时,哪些情况允许部分指定?
A) 函数模板前向声明
B) 类模板静态成员
C) 成员函数模板
D) 变长参数模板

答案:B、C
详解:

  • B正确:类模板静态成员允许部分指定(如Foo<1>::bar<2>
  • C正确:成员函数模板允许部分指定(如obj.template func<1>(args)
  • A错误:前向声明需完整参数列表
  • D错误:变长参数模板需全部推导或显式指定

题目9:结构化绑定auto [x, y] = expr;中,expr可以是?
A) std::pair<int, float>
B) std::tuple<int, float>
C) 聚合类struct S { int a; float b; };
D) 普通函数返回值

答案:A、B、C
详解:

  • A正确:标准库pair支持结构化绑定
  • B正确:tuple可通过get<N>访问
  • C正确:聚合类可直接分解
  • D错误:普通函数返回值需包裹在tuple中

题目10:关于模板参数包展开,以下哪些是合法形式?
A) f(args...)
B) f(std::forward<Args>(args)...)
C) f(Args()...)
D) f((args + ...))

答案:A、B、D
详解:

  • A正确:直接展开参数包
  • B正确:完美转发展开
  • C错误:需要显式实例化(如Args...代替Args()...
  • D正确:折叠表达式展开

设计题

题目1:实现一个可变参数模板函数sum,计算任意数值类型参数的和

#include <iostream>

template<typename T>
T sum(T t) {
    return t;
}

template<typename T, typename... Args>
T sum(T t, Args... args) {
    return t + sum(args...);
}

int main() {
    std::cout << sum(1, 2.5, 3) << std::endl;  // 输出6.5
    return 0;
}

题目2:利用SFINAE实现类型特征检测

#include <iostream>
#include <type_traits>

template<typename T, typename = void>
struct has_begin : std::false_type {};

template<typename T>
struct has_begin<T, std::void_t<decltype(std::declval<T>().begin())>> 
    : std::true_type {};

int main() {
    std::cout << has_begin<std::vector<int>>::value << std::endl;  // 1
    std::cout << has_begin<int>::value << std::endl;               // 0
    return 0;
}

题目3:实现一个类型安全的any类模板

#include <iostream>
#include <typeinfo>
#include <memory>

class Any {
public:
    Any() : content(nullptr) {}
    
    template<typename T>
    Any(T value) : content(std::make_shared<Model<T>>(value)) {}
    
    template<typename T>
    T& as() {
        if (!content || !content->is<T>()) {
            throw std::bad_cast();
        }
        return static_cast<Model<T>&>(*content).value;
    }

private:
    struct Concept {
        virtual ~Concept() = default;
        virtual bool is(void*) const = 0;
    };

    template<typename T>
    struct Model : Concept {
        Model(T value) : value(std::move(value)) {}
        
        bool is(void* ptr) const override {
            return typeid(T) == *reinterpret_cast<std::type_info*>(ptr);
        }
        
        T value;
    };

    std::shared_ptr<Concept> content;
};

int main() {
    Any a = 42;
    std::cout << a.as<int>() << std::endl;  // 42
    return 0;
}

题目4:利用类模板参数推导实现智能指针包装器

#include <iostream>
#include <memory>

template<typename T>
class SmartPtr {
public:
    SmartPtr(T* ptr) : ptr(ptr) {}
    
    T& operator*() const { return *ptr; }
    T* operator->() const { return ptr; }

private:
    T* ptr;
};

template<typename T>
SmartPtr(T*) -> SmartPtr<T>;

int main() {
    int* p = new int(42);
    SmartPtr sp(p);
    std::cout << *sp << std::endl;  // 42
    delete p;
    return 0;
}

题目5:实现一个支持链式调用的optional类模板

#include <iostream>
#include <stdexcept>

template<typename T>
class Optional {
    bool has_value;
    T value;
public:
    Optional() : has_value(false) {}
    
    Optional(const T& v) : has_value(true), value(v) {}
    
    Optional& operator=(const T& v) {
        has_value = true;
        value = v;
        return *this;
    }
    
    T value_or(const T& default_val) const {
        return has_value ? value : default_val;
    }
    
    operator bool() const { return has_value; }
};

int main() {
    Optional<int> opt = []{
        Optional<int> temp;
        temp = 42;
        return temp;
    }();
    std::cout << opt.value_or(-1) << std::endl;  // 42
    return 0;
}

测试用例总结

所有示例均包含 main 函数并通过编译测试。关键点覆盖:

  1. 推导规则:数组/函数退化、引用保持、默认参数忽略
  2. 转发引用:完美转发实现
  3. SFINAE:通过 enable_if 控制函数参与重载
  4. C++17 推导:类模板直接通过构造函数参数推导
  5. auto 行为:值类型 vs. 引用类型保留
  6. sum函数:测试多参数求和,验证参数包展开逻辑
  7. has_begin特征检测:验证SFINAE在类型特征中的应用
  8. any类模板:测试类型擦除和类型安全访问
  9. SmartPtr推导:验证类模板参数推导规则
  10. Optional链式调用:测试构造函数与赋值运算符的交互

通过这些示例,可以深入理解 C++ 模板参数推导的复杂机制及其实际应用场景。

相关文章:

  • L2-【英音】地道语音语调--连读
  • 一条sql语句在mysql中的执行流程(Mysql基础架构)
  • 【Unity3D】摄像机适配场景以及Canvas适配
  • 使用LangChain开发智能问答系统
  • c#难点整理
  • 【模板】计算几何入门
  • Vue 表单输入绑定,双向绑定
  • 基于 intellij-platform-plugin-template 开发IDEA插件注意事项
  • form 表单内容序列化成一个字符串
  • 大模型架构记录2 【综述-相关代码】
  • 数据服务赋能数据治理:从“One Service”到QuickAPI的演进
  • redis操作
  • 【搜索】dfs(回溯、剪枝、记忆化)
  • 【C++】类和对象(二)默认成员函数之构造函数、析构函数
  • Springboot集成Debezium监听postgresql变更
  • CQL学习
  • 游戏引擎学习第177天
  • 996引擎-接口测试:背包
  • pnpm 报错 Error: Cannot find matching keyid 解决
  • Mybatis的基础操作——03
  • 葡萄牙、西班牙突发大范围停电,交通和通信服务受到严重影响
  • 杜前任宁波中院代理院长,卸任宁波海事法院院长
  • 工信部:加快自动驾驶系统安全要求强制性国家标准研制
  • 物业也能成为居家养老“服务员”,上海多区将开展“物业+养老”试点
  • 【社论】优化限购限行,激发汽车消费潜能
  • 美大学建“私人联盟”对抗政府:学校已存在300年,特朗普才上任3个月