为何无法在作用域内声明模板变量或模板typedef?附C++17代码需求
实现符合需求的C++17
foo 函数 嘿,这个需求很实用啊!咱们用C++17的特性就能完美实现这个foo函数,核心是靠编译期类型判断和折叠表达式来区分两种场景。我给你写个完整的实现,再解释下关键部分:
核心思路
要实现这个逻辑,我们需要在编译期完成两个关键判断:
- 所有
fun(x)的返回类型是否都不是void - 根据判断结果,选择返回
tuple或者仅执行调用后返回void
C++17的if constexpr和折叠表达式正好能帮我们搞定这个编译期分支逻辑,不会生成无效的代码。
完整代码实现
#include <tuple> #include <type_traits> #include <utility> template <typename Func, typename... Args> auto foo(Func&& fun, Args&&... args) { // 编译期检查:所有fun(转发后的参数)的返回类型是否都非void if constexpr ((!std::is_void_v<decltype(fun(std::forward<Args>(args)))> && ...)) { // 所有返回值都非void,打包成tuple返回 return std::make_tuple(fun(std::forward<Args>(args))...); } else { // 至少有一个返回值是void,依次执行所有调用后返回void (fun(std::forward<Args>(args)), ...); return; } }
关键细节解释
- 完美转发:用
std::forward<Args>(args)来转发参数,保证原参数的左值/右值特性不会丢失,避免不必要的拷贝或者移动。 - 折叠表达式判断类型:
(!std::is_void_v<decltype(fun(...))> && ...)这个折叠表达式会逐个检查每个fun(args)的返回类型,只有当所有返回类型都不是void时,整个表达式才为true。 - 编译期分支:
if constexpr是C++17的特性,编译器会在编译时就确定走哪个分支,不会为无效分支生成代码(比如当有void返回时,不会尝试生成打包tuple的代码)。 - void场景的调用执行:用
(fun(...), ...)的逗号折叠表达式,确保每个fun(args)都被依次调用,逗号运算符会按顺序求值每个表达式,最后整个表达式的结果不影响,因为我们返回void。
测试示例
你可以用下面的代码验证这个函数的行为:
#include <iostream> // 测试函数1:返回int int add_one(int x) { return x + 1; } // 测试函数2:返回double double multiply_by_two(double x) { return x * 2; } // 测试函数3:返回void void print_int(int x) { std::cout << "Printed: " << x << "\n"; } int main() { // 场景1:所有返回值非void,得到tuple auto result = foo(add_one, 1, 2, 3); static_assert(std::is_same_v<decltype(result), std::tuple<int, int, int>>); std::cout << std::get<0>(result) << ", " << std::get<1>(result) << ", " << std::get<2>(result) << "\n"; // 场景2:混合void和非void返回,仅执行调用 foo([](int x) { std::cout << x << " "; }, 10, 20, 30); std::cout << "\n"; // 场景3:全部返回void,依次执行调用 foo(print_int, 42, 100); return 0; }
针对你实际场景的调整
你提到在实际场景中不能直接用decltype(fun(x)),需要替换成自定义的ReturnType<decltype(x)>。没关系,只需要把判断条件里的decltype(fun(std::forward<Args>(args)))替换成你的自定义返回类型即可。比如:
假设你的自定义返回类型模板是template <typename T> using ReturnType = /* 你的类型推导逻辑 */;,那修改后的判断条件就是:
if constexpr ((!std::is_void_v<ReturnType<Args>> && ...))
代码的整体结构不需要改动,核心的编译期分支逻辑完全通用。
内容的提问来源于stack exchange,提问作者tom
相关产品推荐
相关产品推荐

