如何在CRTP派生实例序列上调用模板函数?求无Lambda的简便方案
问题
现有一个接受符合CRTP基类概念/接口的泛型参数的模板函数:
template <typename Parser_T> void print(Parser_T&& parser)
我希望遍历一系列CRTP派生实例来调用该函数。目前的实现是使用包含所有已知子类的std::variant,结合std::visit与Lambda来调用模板函数。
请问是否有更简便的实现方式,尤其是可以去掉Lambda的方案?我不需要同构存储实例,基于模板技巧的列表也可,但必须能指定实例的调用顺序。
示例代码
#include <iostream> #include <array> #include <variant> #include <string> template <class T> struct parser_base { std::string parse() { return static_cast<T*>(this)->parse_impl(); } }; struct parser_a : parser_base<parser_a> { std::string parse_impl() { return "A\n"; } }; struct parser_b : parser_base<parser_b> { std::string parse_impl() { return "B\n"; } }; template <typename Parser_T> void print(Parser_T&& parser) { std::cout << parser.parse(); } int main() { using var_t = std::variant<parser_a, parser_b>; std::array<var_t,2> parser_list{parser_a{}, parser_b{}}; for(auto &parser: parser_list) { std::visit([](auto &&arg) { print(arg); }, parser); } }
解决方案
方法1:模板参数包直接展开(最简洁,无需variant/Lambda)
如果能在编译期确定要调用的实例类型和顺序,直接用模板参数包展开是最优解——完全不需要Lambda或variant,编译期处理效率最高:
#include <iostream> #include <string> // 原CRTP基类和派生类保持不变 template <class T> struct parser_base { std::string parse() { return static_cast<T*>(this)->parse_impl(); } }; struct parser_a : parser_base<parser_a> { std::string parse_impl() { return "A\n"; } }; struct parser_b : parser_base<parser_b> { std::string parse_impl() { return "B\n"; } }; template <typename Parser_T> void print(Parser_T&& parser) { std::cout << parser.parse(); } // 模板函数:遍历参数包中的每个实例并调用print template <typename... Parsers> void print_all(Parsers&&... parsers) { (print(std::forward<Parsers>(parsers)), ...); } int main() { // 直接按指定顺序传入实例,编译期自动展开调用 print_all(parser_a{}, parser_b{}); }
方法2:去掉Lambda的std::variant方案
如果必须保留variant存储,可以用自定义visitor替代Lambda,彻底去掉匿名函数:
#include <iostream> #include <array> #include <variant> #include <string> // 原CRTP基类、派生类、print函数保持不变 template <class T> struct parser_base { std::string parse() { return static_cast<T*>(this)->parse_impl(); } }; struct parser_a : parser_base<parser_a> { std::string parse_impl() { return "A\n"; } }; struct parser_b : parser_base<parser_b> { std::string parse_impl() { return "B\n"; } }; template <typename Parser_T> void print(Parser_T&& parser) { std::cout << parser.parse(); } // 自定义visitor,替代Lambda struct print_visitor { template <typename Parser_T> void operator()(Parser_T&& parser) const { print(std::forward<Parser_T>(parser)); } }; int main() { using var_t = std::variant<parser_a, parser_b>; std::array<var_t,2> parser_list{parser_a{}, parser_b{}}; for(auto &parser: parser_list) { // 直接传入visitor实例,无需Lambda std::visit(print_visitor{}, parser); } }
方法3:用std::tuple存储实例并遍历
如果需要存储实例但不想用variant,可以用tuple存储不同类型的实例,编译期遍历调用:
#include <iostream> #include <tuple> #include <string> // 原CRTP基类、派生类、print函数保持不变 template <class T> struct parser_base { std::string parse() { return static_cast<T*>(this)->parse_impl(); } }; struct parser_a : parser_base<parser_a> { std::string parse_impl() { return "A\n"; } }; struct parser_b : parser_base<parser_b> { std::string parse_impl() { return "B\n"; } }; template <typename Parser_T> void print(Parser_T&& parser) { std::cout << parser.parse(); } // 遍历tuple的辅助实现 template <typename Tuple, std::size_t... I> void print_tuple_impl(const Tuple& tuple, std::index_sequence<I...>) { (print(std::get<I>(tuple)), ...); } template <typename Tuple> void print_tuple(const Tuple& tuple) { print_tuple_impl(tuple, std::make_index_sequence<std::tuple_size_v<Tuple>>{}); } int main() { // 按指定顺序存储不同类型的实例 auto parsers = std::make_tuple(parser_a{}, parser_b{}); print_tuple(parsers); }
内容的提问来源于stack exchange,提问作者Superlokkus
相关产品推荐
相关产品推荐

