能否为传入函数的泛型lambda显式指定模板参数?
我需要对大小在编译期可知的小型容器(通常4-8个元素)实现编译期循环。实现单个简单循环并不复杂:我可以定义带重载operator()的模板仿函数F,按照如下代码调用:
constexpr std::array<T, N> array{/*fill the array*/}; template <std::size_t index> struct F { void operator()() { std::vector<T> some_container{/*fill it in runtime*/}; some_container[index + some_offset] += std::get<index>(array); // 使用安全的数组访问 } }; template <template<std::size_t> typename F, std::size_t... I> void iterator_implementation( const std::index_sequence<I...> /*unused*/) { ((F<I>{}()), ...); } template <template<std::size_t> typename F> void iterator() { iterator_implementation<F>(std::make_index_sequence<array.size()>{}); }
但如果我需要实现10个这类循环,同时要便捷传入一批引用作为闭包,使用lambda函数会更合适。要在lambda中使用std::get,我需要使用泛型lambda,因此上述示例中的仿函数可以改写为:
auto f = [&some_container, some_offset]<std::size_t index>() { some_container[index + some_offset] += std::get<index>(array); // 使用安全的数组访问 };
现在我无法直接将这个lambda传入iterator函数:因为不指定模板参数的话无法传递模板类型变量,同时我也无法直接获取lambda的类型作为F参数来调用F<I>{}重建lambda。如果我将模板参数从template<std::size_t> typename F改为typename F,我也无法调用f<I>,因为普通类型不携带模板参数,不能直接搭配模板实参使用。
我也可以将std::get<index>(array)和index作为普通参数传入lambda,在保留访问安全性的同时避免使用模板参数,但我希望尽可能使用编译期常量验证其效果。我认为将编译期常量数值作为模板实参传递,比作为普通变量更利于编译器优化(即使智能编译器通常也能识别并优化这类场景),因此本问题主要是技术探索,不讨论该方案的必要性。
请问我能否按照需求在参数包折叠表达式中使用这类泛型lambda?
补充说明:本问题不是泛型lambda传参的重复问题,其他同类问题中模板参数可推导,而本问题需要显式指定模板参数。
完全可以,只需要调整迭代器的实现,直接接收lambda对象并显式调用其模板operator()即可,不需要用模板模板参数重建可调用对象。
方案1:直接调用带显式模板参数的lambda
C++20起支持带模板参数列表的泛型lambda,我们只需要修改迭代器的实现,用.template operator()<I>()的语法显式指定模板参数调用lambda即可,注意这里的template关键字是为了消歧义,告诉编译器后续的<是模板参数列表的分隔符而非小于号:
// 底层实现:接收可调用对象和索引序列 template <typename F, std::size_t... I> void iterator_implementation(F&& f, std::index_sequence<I...> /*unused*/) { // 折叠表达式中显式指定模板参数调用lambda (f.template operator()<I>(), ...); } // 对外接口:指定循环次数,传入可调用对象 template <std::size_t N, typename F> void iterator(F&& f) { iterator_implementation(std::forward<F>(f), std::make_index_sequence<N>{}); }
调用方式非常简单,直接传入你写好的lambda即可:
iterator<array.size()>(f);
这种实现完全保留了你原本的写法,不需要修改lambda的定义,也不需要重建lambda对象,所有捕获的上下文都会正常生效。
方案2:用编译期常量包装类简化语法
如果你觉得.template operator()的语法可读性差,也可以用std::integral_constant把索引包装成编译期常量传入lambda,这种写法不需要显式写模板参数列表,C++17即可支持:
首先修改lambda的定义,接收一个auto类型的常量包装参数:
auto f = [&some_container, some_offset](auto index_constant) { // 从包装类中获取编译期常量索引 constexpr std::size_t index = index_constant.value; some_container[index + some_offset] += std::get<index>(array); };
对应的迭代器实现也可以简化,不需要加消歧义的template关键字:
template <typename F, std::size_t... I> void iterator_implementation(F&& f, std::index_sequence<I...> /*unused*/) { (f(std::integral_constant<std::size_t, I>{}), ...); } template <std::size_t N, typename F> void iterator(F&& f) { iterator_implementation(std::forward<F>(f), std::make_index_sequence<N>{}); }
两种方案的优化效果完全一致,索引全程都是编译期常量,编译器可以做同等程度的优化,你可以根据自己的代码风格选择。
内容的提问来源于stack exchange,提问作者Charlie

