You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

C++20递归版std::invoke_result_t实现遇编译错误求解决方案

解决递归调用结果类型推导的编译错误问题

你的核心问题出在第二个模板特化的约束条件上——它错误地要求容器的元素本身是一个输入范围,但你的测试用例(比如std::vector<int>)里的元素是int,并不是范围类型,导致这个特化完全不匹配,而主模板的约束std::invocable<F, T>又不满足(你的lambda不能直接调用std::vector<int>),最终触发编译错误。

我们需要重新调整特化的逻辑:

  1. 优先处理F可以直接调用T的情况(主模板)
  2. 当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

火山引擎 最新活动