乐于分享
好东西不私藏

C++17 占位符类型作为模板参数(auto 和 decltype(auto))

C++17 占位符类型作为模板参数(auto 和 decltype(auto))

从 C++17 开始,我们可以使用 auto 和 decltype(auto) 作为非类型模板参数的类型,这大大增强了模板的泛型编程能力。

基础用法:auto 作为非类型模板参数

#include<iostream>
#include<type_traits>

// 使用 auto 作为非类型模板参数
template<auto N>
classValueWrapper {
public:
voidprint()const{
std::cout << "Value: " << N 
                  << ", Type: " << typeid(N).name() 
                  << std::endl;
    }

autogetValue()constreturn N; }
};

// 部分特化:针对指针类型
template<auto* Ptr>
classPointerWrapper {
public:
voidprint()const{
std::cout << "Pointer address: " << Ptr << std::endl;
ifconstexpr(!std::is_null_pointer_v<decltype(Ptr)>){
std::cout << "Pointed value: " << *Ptr << std::endl;
        }
    }
};

intmain(){
// 不同类型实例化
    ValueWrapper<42> v1;      // int 类型
    ValueWrapper<'A'> v2;     // char 类型
    ValueWrapper<true> v3;    // bool 类型
    ValueWrapper<3.14f> v4;   // float 类型
    ValueWrapper<&v1> v5;     // 指针类型

    v1.print();  // Value: 42, Type: int
    v2.print();  // Value: A, Type: char
    v3.print();  // Value: 1, Type: bool
    v4.print();  // Value: 3.14, Type: float

// 注意:double 和字符串字面量不能作为非类型模板参数
// ValueWrapper<3.14> v6;  // 错误:double 不是允许的模板参数类型
// ValueWrapper<"hello"> v7; // 错误:字符串字面量不能作为模板参数

return0;
}

结合类型推导和部分特化

#include<iostream>
#include<array>

// 主模板:通用处理
template<auto N>
structFixedArray {
staticconstexprauto size = N;
using value_type = decltype(N);

voidinfo()const{
std::cout << "通用版本 - 大小: " << size 
                  << ", 类型: " << typeid(value_type).name() << std::endl;
    }
};

// 部分特化:针对 int 类型
template<int N>
structFixedArray<N> {
staticconstexprauto size = N;

voidinfo()const{
std::cout << "int 特化版本 - 大小: " << size << std::endl;
    }

std::array<int, N> getArray()const{
std::array<int, N> arr{};
for (int i = 0; i < N; ++i) arr[i] = i;
return arr;
    }
};

// 部分特化:针对枚举类型
enumclassColor { Red, Green, Blue };

template<Color C>
structFixedArray<C> {
voidinfo()const{
std::cout << "枚举特化版本 - 颜色: ";
switch(C) {
case Color::Red: std::cout << "红"break;
case Color::Green: std::cout << "绿"break;
case Color::Blue: std::cout << "蓝"break;
        }
std::cout << std::endl;
    }
};

intmain(){
    FixedArray<42> fa1;      // 匹配 int 特化版本
    FixedArray<'X'> fa2;     // 使用通用版本(char)
    FixedArray<100ull> fa3;  // 使用通用版本(unsigned long long)
    FixedArray<Color::Green> fa4;  // 匹配枚举特化版本

    fa1.info();  // int 特化版本 - 大小: 42
    fa2.info();  // 通用版本 - 大小: X, 类型: char
    fa3.info();  // 通用版本 - 大小: 100, 类型: y (unsigned long long)
    fa4.info();  // 枚举特化版本 - 颜色: 绿

auto arr = fa1.getArray();
std::cout << "数组元素: " << arr[0] << ", " << arr[5] << std::endl;

return0;
}

高级用法:decltype(auto) 和类模板参数推导

#include<iostream>
#include<vector>
#include<array>

// 使用 decltype(auto) 保持精确类型
template<decltype(auto) N>
structPreciseType {
voidprint()const{
std::cout << "值: " << N 
                  << ", 精确类型: " << typeid(decltype(N)).name() 
                  << std::endl;
    }
};

// 结合类模板参数推导
template<typename T, auto N>
classMatrix {
private:
std::array<T, N * N> data;

public:
// 构造函数模板
template<typename... Args>
Matrix(Args&&... args) : data
{std::forward<Args>(args)...} {}

voidprint()const{
for (size_t i = 0; i < N; ++i) {
for (size_t j = 0; j < N; ++j) {
std::cout << data[i * N + j] << " ";
            }
std::cout << std::endl;
        }
    }

autogetSize()constreturn N; }
};

// C++17 类模板参数推导指引
template<typename T, auto N>
Matrix(T(&)[N * N]) -> Matrix<T, N>
;

intmain(){
// decltype(auto) 示例
int x = 42;
int& ref = x;

    PreciseType<42> pt1;      // 值类型
    PreciseType<(ref)> pt2;   // 引用类型(注意括号)

    pt1.print();  // 值: 42, 精确类型: int
    pt2.print();  // 值: 42, 精确类型: int

// 类模板参数推导示例
int arr1[] = {123456789};  // 3x3 矩阵
Matrix m1(arr1);  // 推导出 Matrix<int, 3>
    m1.print();
std::cout << "矩阵大小: " << m1.getSize() << "x" << m1.getSize() << std::endl;

// 直接指定类型
    Matrix<double2> m2{1.02.03.04.0};
    m2.print();

return0;
}

实际应用:编译期计算和类型转换

#include<iostream>
#include<tuple>

// 编译期整数序列生成
template<auto Start, auto End, auto Step = 1>
struct IntegerRange {
staticconstexprauto size = (End - Start + Step - 1) / Step;

template<typename F>
staticconstexprvoidforEach(F&& func)
{
ifconstexpr(Start < End){
            func(Start);
            IntegerRange<Start + Step, End, Step>::forEach(std::forward<F>(func));
        }
    }

staticconstexprautogetArray(){
std::array<int, size> arr{};
for (size_t i = 0; i < size; ++i) {
            arr[i] = Start + i * Step;
        }
return arr;
    }
};

// 编译期单位转换
template<auto Value>
structUnit {
staticconstexprauto value = Value;

template<typename TargetUnit>
staticconstexprautoconvert()
{
ifconstexpr(std::is_same_v<decltype(Value), constint&>){
return Value;  // 保持原值
        } else {
return Value;
        }
    }
};

// 使用 decltype(auto) 保存 constexpr lambda
template<auto Func>
structFunctionWrapper {
staticconstexprautoapply(auto&&... args){
return Func(std::forward<decltype(args)>(args)...);
    }
};

intmain(){
// 整数序列示例
using Range = IntegerRange<0102>;
std::cout << "序列大小: " << Range::size << std::endl;

std::cout << "序列元素: ";
    Range::forEach([](auto elem) {
std::cout << elem << " ";
    });
std::cout << std::endl;

auto arr = Range::getArray();
std::cout << "数组: ";
for (auto v : arr) std::cout << v << " ";
std::cout << std::endl;

// 单位转换示例
    Unit<100> u1;
    Unit<3.14f> u2;
std::cout << "u1: " << u1.value << std::endl;
std::cout << "u2: " << u2.value << std::endl;

// C++20 后支持 constexpr lambda 作为模板参数
constexprauto square = [](auto x) { return x * x; };
    FunctionWrapper<square> wrapper;
std::cout << "平方: " << wrapper.apply(5) << std::endl;

return0;
}

重要限制

  1. 允许的类型:整数类型、枚举类型、指针、引用、std::nullptr_t
  2. 不允许的类型:浮点数(除 float 外)、字符串字面量、自定义类型(C++20 前)
// 允许的示例
template<auto N> classC1 {};     // 各种整数类型
template<auto* P> classC2 {};    // 指针
template<auto& R> classC3 {};    // 引用
template<auto E> classC4 {};     // 枚举

// 不允许的示例(C++17 中)
// template<auto D> class C5 {};  // double 不允许(float 允许)
// template<auto S> class C6 {};  // 字符串字面量不允许
// template<auto S> class C7 {};  // std::string 不允许

总结

auto 和 decltype(auto) 作为模板参数极大地提高了代码的泛用性,特别是在:

  • 编译期计算和元编程
  • 泛型容器和算法
  • 类型安全的常量传递
  • 与 C++17 类模板参数推导的协同工作

这使得我们可以编写更简洁、更通用的模板代码,同时保持类型安全。