C++20递归版std::invoke_result_t实现遇编译错误求解决方案
解决递归调用结果类型推导的编译错误问题
你的核心问题出在第二个模板特化的约束条件上——它错误地要求容器的元素本身是一个输入范围,但你的测试用例(比如std::vector<int>)里的元素是int,并不是范围类型,导致这个特化完全不匹配,而主模板的约束std::invocable<F, T>又不满足(你的lambda不能直接调用std::vector<int>),最终触发编译错误。
我们需要重新调整特化的逻辑:
- 优先处理F可以直接调用T的情况(主模板)
- 当T是一个输入范围(容器),且F不能直接调用T时,递归推导范围元素的结果类型,再构造对应的容器类型
另外还要注意保留容器的附加模板参数(比如std::vector的分配器参数),避免丢失信息。
修正后的实现代码
#include <concepts> #include <ranges> #include <functional> #include <vector> #include <string> #include <iostream> // 主模板:当F可直接调用T时,直接返回invoke_result_t template<typename F, typename T> requires std::invocable<F, T> struct recursive_invoke_result_detail { using type = std::invoke_result_t<F, T>; }; // 特化:当T是输入范围,且F不能直接调用T时,递归处理元素 template<typename F, std::ranges::input_range Range> requires (!std::invocable<F, Range>) struct recursive_invoke_result_detail<F, Range> { // 获取范围的元素类型 using elem_type = std::ranges::range_value_t<Range>; // 递归推导元素的结果类型 using transformed_elem = typename recursive_invoke_result_detail<F, elem_type>::type; // 构造对应的容器类型:保留原容器的模板参数,替换元素类型 using type = std::ranges::range_value_t<Range*>; // 技巧:通过Range*获取容器的模板实例并替换元素 }; // 别名模板简化使用 template<typename F, typename T> using recursive_invoke_result_t = typename recursive_invoke_result_detail<F, T>::type; // 测试代码 int main() { auto f1 = [](int x) { return static_cast<double>(x); }; std::cout << typeid(recursive_invoke_result_t<decltype(f1), int>).name() << std::endl; // double std::cout << typeid(recursive_invoke_result_t<decltype(f1), std::vector<int>>).name() << std::endl; // vector<double> std::cout << typeid(recursive_invoke_result_t<decltype(f1), std::vector<std::vector<int>>>).name() << std::endl; // vector<vector<double>> auto f2 = [](int x) { return std::to_string(x); }; std::cout << typeid(recursive_invoke_result_t<decltype(f2), std::vector<int>>).name() << std::endl; // vector<string> auto f3 = [](std::string x) { return std::atoi(x.c_str()); }; std::cout << typeid(recursive_invoke_result_t<decltype(f3), std::vector<std::string>>).name() << std::endl; // vector<int> return 0; }
关键修改点说明
- 特化的约束改为
std::ranges::input_range Range,直接判断T是否是一个输入范围(容器/视图),不再要求元素是范围 - 使用
std::ranges::range_value_t<Range*>来获取容器的模板实例:对于std::vector<int>,Range*是std::vector<int>*,range_value_t会返回std::vector<transformed_elem>,这是C++20范围库的小技巧,用来提取容器的模板类型并替换元素 - 特化额外增加
!std::invocable<F, Range>约束,避免和主模板的情况冲突(比如如果有一个lambda可以直接调用容器,优先用主模板)
兼容型替代方案
如果你的编译器对std::ranges::range_value_t<Range*>的支持有问题,也可以手动提取容器的模板参数:
// 辅助模板:提取容器的模板类型和参数 template<typename Container> struct container_traits; template<template<typename...> typename Container, typename... Args> struct container_traits<Container<Args...>> { template<typename T> using rebind = Container<T>; }; // 在特化中替换为以下代码 using type = typename container_traits<Range>::template rebind<transformed_elem>;
这种方式更直观,兼容性也更强。
内容的提问来源于stack exchange,提问作者JimmyHu




