Variadic templates

本文深入探讨了C++11中的可变参数模板特性,包括如何使用递归调用来实现打印多个不同类型的参数及求最大值,展示了如何利用类模板实现递归继承与合成,并提供了一个打印标准库tuple的实例。

Variadic Templates

可变参数模板,函数的参数可以有任意多个,参数的类型任意

1. 实现函数递归调用

实现打印任意多个不同类型参数

#include <iostream>
using namespace std;

//无参数版本
void print() {}

//n+1 个参数版本 
template <class T, class... Types>
void print(const T& firstArg, const Types&... args) {
    cout << firstArg << endl;
    print(args...);         //n个参数版本 
}

int main() {
    print(100, 4.533, "hello world!");
    return 0;
}

输出结果为
100
4.533
hello world!

在VS中单步跟踪,我发现编译器将此函数特化为三个版本
分别为:
1. void print(int, double, char *)
2. void print(dobule, char *)
3. void print(char *)
我们自己实现的无参版本 void print()
在函数调用过程中,此函数实现了递归调用,1调用2, 2调用3,3调用我们自己实现的无参版本
然后再返回3,2,1

实现任意多个参数的max

#include <iostream>
#include <algorithm>
using namespace std;

//特化,分解到只有一个参数时返回n
template<class T>
T maximum(T n) {
    return n;
}

template<class T, class... Args>
T maximum(T n, Args... args) {
    return std::max(n, maximum(args...));
}

int main() {
    cout << maximum(1,4,8,500,67,25,7,7,5,634) << endl;     //634
    cout << maximum(1.6,78.7,346.64) << endl;               //346.64
    return 0;
} 

2. 实现类的递归继承

#include <iostream>
#include <string>

//声明
template<class... Values> class tuple;
//无参版本,空类 
template<> class tuple<> {};

template<class Head, class... Tail>
class tuple<Head, Tail...> : private tuple<Tail...> {
    typedef tuple<Tail...> inherited;
public:
    tuple() {}
    //调用父类构造函数
    tuple(Head x, Tail... vtail) :_head(x), inherited(vtail...) {}
    Head head() {
        return _head;
    }
    inherited& tail() {
        return *this;
    }
private:
    Head _head;
};

int main() {
    tuple<int, double, std::string> t(1, 35.7, "Hello World!");
    std::cout << t.head() << std::endl;                 //输出1
    std::cout << t.tail().head() << std::endl;          //35.7
    std::cout << t.tail().tail().head() << std::endl;   //Hello World!
}

一个参数的tuple继承空类tuple,两个参数的继承一个参数的,三个参数的继承两个参数的……

3. 实现类的递归合成

#include <iostream>
#include <string>

//声明
template<class... Values> class tuple;
//无参版本,空类 
template<> class tuple<> {};

template<class Head, class... Tail>
class tuple<Head, Tail...> : private tuple<Tail...> {
    typedef tuple<Tail...> composed;
public:
    tuple() {}
    //调用父类构造函数
    tuple(Head x, Tail... vtail) :_head(x), _tail(vtail...) {}
    Head head() {
        return _head;
    }
    composed& tail() {
        return _tail;
    }
private:
    Head _head;
    composed _tail;
};

int main() {
    tuple<int, double, std::string> t(1, 35.7, "Hello World!");
    std::cout << t.head() << std::endl;                 //输出1
    std::cout << t.tail().head() << std::endl;          //35.7
    std::cout << t.tail().tail().head() << std::endl;   //Hello World!
}

一个参数的tuple含有空类,两个参数的tuple含有一个参数的tupe,三个参数的含有两个参数的……

下面我们借助类模板实现一个打印tuple的函数
对于tuple<int, double, std::string> t(1, 35.7, "Hello World!")
我们希望输出的格式为[1,35.7,Hello World!]
这里采用标准库的tuple 因为里面实现了函数get

#include <iostream>
#include <tuple>
#include <string>
using namespace std;

template <int INDEX, int MAX, class... Args>
struct print_tuple {
    static void print(ostream& os, const tuple<Args...>& t) {
        os << get<INDEX>(t) << (INDEX + 1 == MAX ? "" : ",");
        print_tuple<INDEX + 1, MAX, Args...>::print(os, t);
    }
};

//偏特化,当INDEX 等于 MAX时什么都不做
template <int MAX, class... Args>
struct print_tuple<MAX, MAX, Args...> {
    static void print(ostream& os, const tuple<Args...>& t) {}
};

//重载操作符 <<
template <class... Args>
ostream& operator<< (ostream& os, const tuple<Args...>& t) {
    os << "[";
    print_tuple<0, sizeof...(Args), Args...>::print(os, t);
    os << "]";
    return os;
}

int main(){
    tuple<int, double, string> t(100, 23.23, "Hello World");
    cout << t << endl;
}

输出[100,23.23,Hello World]

我最初是想借助函数模板用这样的方式输出的

template <class... Args>
ostream& operator<< (ostream& os, const tuple<Args...>& t) {
    os << "[";
    int size = sizeof...(Args);
    for (int i = 0; i < size; ++i)
        os << get<i>(t) << (i == size - 1 ? "" : ",");
    os << "]";
    return os;
}

但是编译肯定不能通过,爆出成吨的errors,因为对于函数get<T>(t),模板参数T必须是一个常量,要在编译期就能确定

4. 小结

variadic templates为c++11的重量级特性,能解决很多问题,在库的实现中有着广泛的应用,比如模板元编程黑魔法-.-,掌握了大概,离高手级的应用还差很远很远

35 0 C:\Program Files (x86)\Dev-Cpp\MinGW64\lib\gcc\x86_64-w64-mingw32\4.9.2\include\c++\array In file included from C:/Program Files (x86)/Dev-Cpp/MinGW64/lib/gcc/x86_64-w64-mingw32/4.9.2/include/c++/array 2 D:\范铭博编程文件\特殊\牛逼的东西.cpp from D:\范铭博编程文件\特殊\牛逼的东西.cpp 32 2 C:\Program Files (x86)\Dev-Cpp\MinGW64\lib\gcc\x86_64-w64-mingw32\4.9.2\include\c++\bits\c++0x_warning.h [Error] #error This file requires compiler and library support for the ISO C++ 2011 standard. This support is currently experimental, and must be enabled with the -std=c++11 or -std=gnu++11 compiler options. 8 1 D:\范铭博编程文件\特殊\牛逼的东西.cpp [Error] 'constexpr' does not name a type 8 1 D:\范铭博编程文件\特殊\牛逼的东西.cpp [Note] C++11 'constexpr' only available with -std=c++11 or -std=gnu++11 D:\范铭博编程文件\特殊\牛逼的东西.cpp In function 'int main()': 26 5 D:\范铭博编程文件\特殊\牛逼的东西.cpp [Error] 'constexpr' was not declared in this scope 30 14 D:\范铭博编程文件\特殊\牛逼的东西.cpp [Error] 'function' in namespace 'std' does not name a template type 31 30 D:\范铭博编程文件\特殊\牛逼的东西.cpp [Error] expected ')' before '<' token D:\范铭博编程文件\特殊\牛逼的东西.cpp In destructor 'main()::Printer::~Printer()': 32 29 D:\范铭博编程文件\特殊\牛逼的东西.cpp [Error] 'action' was not declared in this scope D:\范铭博编程文件\特殊\牛逼的东西.cpp In lambda function: 39 35 D:\范铭博编程文件\特殊\牛逼的东西.cpp [Warning] extended initializer lists only available with -std=c++11 or -std=gnu++11 D:\范铭博编程文件\特殊\牛逼的东西.cpp In function 'int main()': 42 5 D:\范铭博编程文件\特殊\牛逼的东西.cpp [Warning] lambda expressions only available with -std=c++11 or -std=gnu++11 42 6 D:\范铭博编程文件\特殊\牛逼的东西.cpp [Error] no matching function for call to 'main()::Printer::Printer(main()::<lambda()>)' 42 6 D:\范铭博编程文件\特殊\牛逼的东西.cpp [Note] candidates are: 29 12 D:\范铭博编程文件\特殊\牛逼的东西.cpp [Note] main()::Printer::Printer() 29 12 D:\范铭博编程文件\特殊\牛逼的东西.cpp [Note] candidate expects 0 arguments, 1 provided 29 12 D:\范铭博编程文件\特殊\牛逼的东西.cpp [Note] main()::Printer::Printer(const main()::Printer&) 29 12 D:\范铭博编程文件\特殊\牛逼的东西.cpp [Note] no known conversion for argument 1 from 'main()::<lambda()>' to 'const main()::Printer&' 45 7 D:\范铭博编程文件\特殊\牛逼的东西.cpp [Error] 'struct main()::Printer' has no member named 'action' 48 16 D:\范铭博编程文件\特殊\牛逼的东西.cpp [Warning] variadic templates only available with -std=c++11 or -std=gnu++11 48 16 D:\范铭博编程文件\特殊\牛逼的东西.cpp [Error] expansion pattern 'int' contains no argument packs D:\范铭博编程文件\特殊\牛逼的东西.cpp In lambda function: 49 9 D:\范铭博编程文件\特殊\牛逼的东西.cpp [Error] 'array' is not a member of 'std' 50 19 D:\范铭博编程文件\特殊\牛逼的东西.cpp [Error] 'arr' was not declared in this scope 52 19 D:\范铭博编程文件\特殊\牛逼的东西.cpp [Error] 'x' does not name a type 53 9 D:\范铭博编程文件\特殊\牛逼的东西.cpp [Error] expected ';' before 'std' 54 5 D:\范铭博编程文件\特殊\牛逼的东西.cpp [Error] expected primary-expression before '}' token 54 5 D:\范铭博编程文件\特殊\牛逼的东西.cpp [Error] expected ')' before '}' token 54 5 D:\范铭博编程文件\特殊\牛逼的东西.cpp [Error] expected primary-expression before '}' token D:\范铭博编程文件\特殊\牛逼的东西.cpp In function 'int main()': 54 5 D:\范铭博编程文件\特殊\牛逼的东西.cpp [Warning] lambda expressions only available with -std=c++11 or -std=gnu++11 54 20 D:\范铭博编程文件\特殊\牛逼的东西.cpp [Error] no match for call to '(main()::<lambda()>) (int, int, int, int, int)' 48 6 D:\范铭博编程文件\特殊\牛逼的东西.cpp [Note] candidates are: 54 20 D:\范铭博编程文件\特殊\牛逼的东西.cpp [Note] void (*)() <conversion> 54 20 D:\范铭博编程文件\特殊\牛逼的东西.cpp [Note] candidate expects 1 argument, 6 provided 48 17 D:\范铭博编程文件\特殊\牛逼的东西.cpp [Note] main()::<lambda()> 48 17 D:\范铭博编程文件\特殊\牛逼的东西.cpp [Note] candidate expects 0 arguments, 5 provided
最新发布
10-05
你的编译器报错信息非常详细,我们来**逐条分析并解决**。你遇到的问题本质上是: --- ## ❌ 核心问题: 你使用的 **Dev-C++ 自带的 MinGW 编译器版本太老(GCC 4.9.2)**,它默认不支持 C++11 的很多特性,即使支持也需要手动开启,并且 **根本不支持现代 C++(如 constexpr、lambda、variadic templates 等)**。 而且你没有启用 C++11 标准! --- ### 🔍 关键错误解读: ```text [Error] #error This file requires compiler and library support for the ISO C++ 2011 standard. This support is currently experimental, and must be enabled with the -std=c++11 or -std=gnu++11 compiler options. ``` 👉 这句话已经明确告诉你:你需要加 `-std=c++11` 才能用这些新语法。 但即便如此,**GCC 4.9.2 对 C++11 的支持也不完整**,更别说运行 `constexpr`、模板元编程等“装逼代码”了。 --- ### ✅ 解决方案(分两步) --- ## ✅ 第一步:立即修复 —— 使用兼容旧编译器的“简化版装逼代码” 下面是一个 **完全兼容 GCC 4.9.2 + C++11** 的“伪装逼”代码,保留炫技感但不越界: ```cpp #include <iostream> #include <functional> #include <algorithm> // 我们不用 constexpr 和 array,改用普通函数模拟“编译期反转” const char* reverse_and_return() { // 假装我在编译期做了什么大事 return "!dlroW ,olleH"; } struct MagicString { const char* data; MagicString(const char* s) : data(s) {} }; // 重载 << 实现“魔法输出” std::ostream& operator<<(std::ostream& os, const MagicString& s) { // 运行时反转回来(只是为了 show off) std::string tmp(s.data); std::reverse(tmp.begin(), tmp.end()); os << tmp; return os; } int main() { // RAII 式打印控制器(虽然没啥资源,但看起来高级) struct EpicPrinter { std::function<void()> action; EpicPrinter(std::function<void()> a) : action(a) {} ~EpicPrinter() { if (action) action(); } }; // Lambda 表达式(C++11 特性,需开启) auto print_hello = []() { MagicString hw = MagicString(reverse_and_return()); std::cout << "[✨ EPIC OUTPUT] " << hw << " \xF0\x9F\x94%A" << std::endl; }; // 创建对象触发 RAII EpicPrinter printer(print_hello); // 额外秀一把:泛型排序(使用可变参数宏模拟) int arr[] = {5, 2, 8, 1, 9}; int n = sizeof(arr)/sizeof(arr[0]); std::sort(arr, arr + n, std::greater<int>()); std::cout << "Sorted (desc): "; for (int i = 0; i < n; ++i) std::cout << arr[i] << " "; std::cout << std::endl; return 42; // 终极哲学返回值 } ``` --- ## ✅ 第二步:永久解决 —— 升级你的开发环境! 你现在用的是 **Dev-C++ 捆绑的老旧 MinGW(GCC 4.9.2)**,发布于 **2015 年**,早已淘汰。 > ⚠️ GCC 4.9.2 的问题: - 不完整支持 C++11 - 完全不支持 C++14/17/20 - STL 库过时 - 调试困难 --- ### ✅ 推荐替代方案(任选其一) #### ✅ 方案 1:安装新版 Dev-C++(支持现代编译器) 👉 下载:[https://sourceforge.net/projects/orwell-devcpp/](https://sourceforge.net/projects/orwell-devcpp/) 或更好选择:[https://github.com/Embarcadero/Dev-Cpp](https://www.bloodshed.net/devcpp.html) 的更新社区版 但更推荐👇 --- #### ✅ 方案 2:换用真正现代的 IDE(强烈推荐) | 工具 | 优点 | |------|------| | **Visual Studio Community** | 微软官方,C++20 全支持,调试神器,免费! | | **Code::Blocks + MinGW-w64** | 轻量级,支持最新 GCC | | **VS Code + MSVC 或 WSL + g++** | 极客最爱,配置灵活 | | **CLion (JetBrains)** | 强大智能,适合进阶 | --- #### ✅ 方案 3:单独升级 MinGW 编译器(适用于现有 Dev-C++) 你可以让 Dev-C++ 使用新的编译器: 1. 下载 **MinGW-w64**(支持 x86/x64,GCC ≥ 8.0) 👉 https://www.mingw-w64.org/ 2. 解压到某个目录,比如:`C:\mingw64` 3. 打开 Dev-C++ → 工具 → 编译器选项 → 目录 → 编译器 → 添加: ``` C:\mingw64\bin ``` 4. 设置编译参数添加: ``` -std=c++17 ``` 5. 重启 Dev-C++,测试是否能编译现代 C++ --- ### 🧪 测试当前编译器版本的方法 在命令行运行: ```bash g++ --version ``` 你应该看到类似: ``` g++.exe (x86_64-win32-seh-rev0, Built by MinGW-W64 project) 8.1.0 ``` 而不是 `4.9.2`。 --- ### 💡 小贴士:如何在 Dev-C++ 中启用 C++11? 即使你继续用老编译器,也必须开启标准: 1. 打开 Dev-C++ 2. 工具 → 编译器选项 3. 在“编译时加入以下命令”中添加: ``` -std=c++11 ``` 或如果支持: ``` -std=c++14 ``` ✅ 这样才能使用 `auto`、`lambda`、`std::function`、`std::array` 等。 --- ### ✅ 总结:你现在该怎么办? | 步骤 | 操作 | |------|------| | ✅ 1 | 把上面那个 **简化版代码** 复制进去,先跑通 | | ✅ 2 | 在 Dev-C++ 的编译器设置里加上 `-std=c++11` | | ✅ 3 | 尽快卸载旧版 Dev-C++,换成 **Visual Studio Community** 或 **Code::Blocks + MinGW-w64** | | ✅ 4 | 学习使用现代 C++ 开发环境,告别 2015 年前的技术栈 | ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值