You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现可调用任意模板Lambda的invoke函数?兼容多编译器

问题描述

我定义了value_t结构体用于将值包装为类型:

template <auto _v>
struct value_t { static constexpr auto value = _v; };

并通过take_off提取类型或值:

template <typename T>
struct take_off { using magic = T; };
template <auto _v>
struct take_off<value_t<_v>> { static constexpr auto magic = _v; };

现在希望在C20或C23中实现invoke函数,用于调用任意带模板参数和函数参数的Lambda,示例调用如下:

auto test_fn = []<typename T, typename T2, T... Vs>(int a, int b){
    (..., (std::cout << (T2)Vs << std::endl));
    std::cout << a << b << std::endl;
};

invoke<int, double, value_t<1>, value_t<2>, value_t<5>>(test_fn, 1, 2);

当前的invoke实现如下:

template <typename... Ts>
decltype(auto) invoke(auto _fn, auto... _args) {
    return _fn.template operator()<take_off<Ts>::magic...>(_args...);
}

该实现在MSVC中可正常工作,但在Clang编译时报错:

error: missing 'typename' prior to dependent type name take_off<Ts>::magic

若添加typename改为typename take_off<Ts>::magic,则invoke无法再支持auto模板参数。

请问是否存在可行的invoke实现方案?例如限制模板参数最大数量为8个,有没有办法简化实现,避免需要枚举3^8种情况的繁琐方式?


解决方案

核心问题是take_off<Ts>::magic既可能是类型(需要typename修饰)也可能是值(不能加typename),属于依赖名称的二义性问题。我们可以利用C++20的requires表达式和模板元编程自动区分类型/值参数,不需要枚举所有参数组合,以下是可行实现:

完整代码实现

#include <type_traits>
#include <iostream>
#include <utility>

// 原有的value_t和take_off保持不变
template <auto _v>
struct value_t { static constexpr auto value = _v; };

template <typename T>
struct take_off { using magic = T; };
template <auto _v>
struct take_off<value_t<_v>> { static constexpr auto magic = _v; };

// 辅助元函数:判断take_off<T>::magic是类型还是值,并提取对应内容
template <typename T>
struct magic_info {
    // 检查是否为类型参数
    static constexpr bool is_type = requires { typename take_off<T>::magic; };
    // 提取类型(如果是类型参数)
    using type = std::conditional_t<is_type, typename take_off<T>::magic, void>;
    // 提取值(如果是值参数)
    static constexpr auto value = std::conditional_t<!is_type, decltype(take_off<T>::magic), std::nullptr_t>{};
};

// 辅助模板:统一包装每个参数的类型/值信息
template <typename T>
struct magic_arg {};

// 针对类型参数的特化
template <typename T>
requires magic_info<T>::is_type
struct magic_arg<T> {
    using type = magic_info<T>::type;
};

// 针对值参数的特化
template <typename T>
requires (!magic_info<T>::is_type)
struct magic_arg<T> {
    static constexpr auto value = magic_info<T>::value;
};

// 内部实现:拆分类型和值参数并传递给目标Lambda
template <typename... Ts, std::size_t... Idx>
decltype(auto) invoke_impl(auto _fn, auto&&... _args, std::index_sequence<Idx...>) {
    // 定义辅助Lambda,接收显式的类型和值模板参数
    return []<typename... Types, auto... Values>(auto fn, auto&&... args) -> decltype(auto) {
        return fn.template operator()<Types..., Values...>(std::forward<decltype(args)>(args)...);
    }.template operator()<typename magic_arg<Ts>::type..., magic_arg<Ts>::value...>(
        _fn, std::forward<decltype(_args)>(_args)...
    );
}

// 对外接口函数
template <typename... Ts>
decltype(auto) invoke(auto _fn, auto&&... _args) {
    return invoke_impl<Ts...>(
        _fn, std::forward<decltype(_args)>(_args)..., 
        std::index_sequence_for<Ts...>{}
    );
}

// 测试代码
auto test_fn = []<typename T, typename T2, T... Vs>(int a, int b){
    (..., (std::cout << (T2)Vs << std::endl));
    std::cout << a << b << std::endl;
};

int main() {
    invoke<int, double, value_t<1>, value_t<2>, value_t<5>>(test_fn, 1, 2);
    return 0;
}

实现原理

  1. magic_info元函数:通过requires表达式判断take_off<T>::magic是类型还是值,分别提取对应的类型或值内容。
  2. magic_arg模板:根据magic_info的结果,对类型参数和值参数进行特化,统一对外暴露type或value成员。
  3. invoke_impl内部函数:利用索引序列遍历所有参数,将类型参数和值参数分别提取后,传递给辅助Lambda的模板参数列表,避免了typename的二义性问题。
  4. 对外接口invoke:封装内部实现,提供简洁的调用方式。

该方案无需限制参数数量,也不需要枚举所有参数组合,在Clang、MSVC等主流编译器的C++20模式下均可正常编译运行。


内容的提问来源于stack exchange,提问作者NoEvaa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 05:14:54