如何实现可调用任意模板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; }
实现原理
magic_info元函数:通过requires表达式判断take_off<T>::magic是类型还是值,分别提取对应的类型或值内容。magic_arg模板:根据magic_info的结果,对类型参数和值参数进行特化,统一对外暴露type或value成员。invoke_impl内部函数:利用索引序列遍历所有参数,将类型参数和值参数分别提取后,传递给辅助Lambda的模板参数列表,避免了typename的二义性问题。- 对外接口
invoke:封装内部实现,提供简洁的调用方式。
该方案无需限制参数数量,也不需要枚举所有参数组合,在Clang、MSVC等主流编译器的C++20模式下均可正常编译运行。
内容的提问来源于stack exchange,提问作者NoEvaa
相关产品推荐
相关产品推荐

