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

C++编译时动态构建列表并在运行时作为const数组使用的可行性及实现方案咨询

编译时构建路由处理列表的可行方案

嘿,你的需求我完全get到了——就是想写那种清爽的server.get<"/path">(handler)风格代码,同时在编译时用CTRE生成正则,还要把这些路由规则整成一个编译期确定的只读数组,运行时直接遍历对吧?之前你尝试的Container类栽在了constexpr里用std::vector的size()上,这很正常,因为哪怕C++20支持constexpr vector,它的大小还是运行时动态的,没法当成编译时常量来用。下面给你几个实打实的解决方案:


方案1:用模板元编程搭编译期路由列表

这种方式完全在编译期构建类型列表,不依赖任何运行时容器,完美匹配你的API风格需求:

首先我们先定义基础的路由处理结构,顺便用CTRE生成编译期正则:

#include <ctre.hpp>
#include <functional>
#include <array>
#include <variant>

// 单个路由的结构,模板参数是编译期路径字符串
template<auto Path>
struct RouteHandler {
    // 编译期生成正则
    constexpr static auto regex = ctre::regex<Path>;
    std::function<void()> handler;

    constexpr RouteHandler(std::function<void()> h) : handler(std::move(h)) {}
};

// 用来装路由的编译期类型列表
template<typename... Routes>
struct RouteList {};

// 辅助模板:给列表加新路由
template<typename List, typename NewRoute>
struct AppendRoute;

template<typename... Routes, typename NewRoute>
struct AppendRoute<RouteList<Routes...>, NewRoute> {
    using type = RouteList<Routes..., NewRoute>;
};

// 用CRTP实现链式调用的Server构建器
template<typename Impl>
class ServerBuilder {
public:
    // 就是你想要的get<路径>(handler)接口
    template<auto Path, typename Handler>
    constexpr auto get(Handler&& handler) && {
        using NewRoute = RouteHandler<Path>;
        using NewList = typename AppendRoute<typename Impl::RouteList, NewRoute>::type;
        // 把之前的路由和新路由打包,返回新的构建器
        return ServerBuilder<Server<NewList>>{
            std::move(static_cast<Impl*>(this)->m_routes),
            NewRoute{std::forward<Handler>(handler)}
        };
    }

    // 构建最终的Server对象
    constexpr auto build() && {
        return static_cast<Impl*>(this)->build_server();
    }
};

// 空的初始构建器
struct EmptyServer {
    using RouteList = RouteList<>;
    std::variant<RouteList> m_routes;

    constexpr auto build_server() {
        return Server<RouteList<>>{};
    }
};

// 最终的Server类,接收编译期路由列表
template<typename... Routes>
class Server<RouteList<Routes...>> {
public:
    // 把所有路由装进std::array(编译期确定大小)
    constexpr Server(Routes... routes) : m_routes{std::move(routes)...} {}

    void listen() {
        // 运行时直接遍历这个const数组就行
        for (const auto& route : m_routes) {
            // 示例:用编译期正则匹配请求路径,匹配就调用handler
            // if (ctre::match<decltype(route)::regex>(request_path)) {
            //     route.handler();
            // }
        }
    }

private:
    std::array<std::variant<Routes...>, sizeof...(Routes)> m_routes;
};

用的时候就完全是你想要的样子了:

int main() {
    auto server = EmptyServer{}
        .get<"/hello">([] { std::cout << "hello\n"; })
        .get<"/goodbye">([] { std::cout << "goodbye\n"; })
        .build();
    server.listen();
}

方案2:用C++20 constexpr std::array简化实现

如果觉得模板元编程有点重,用C++20的constexpr数组是更轻量的选择,虽然API稍微绕一点,但足够简洁:

#include <ctre.hpp>
#include <functional>
#include <array>

// 统一的路由类型,把正则和handler打包
struct Route {
    ctre::regex<> regex;
    std::function<void()> handler;
};

// 辅助函数:编译期生成路由
template<auto Path, typename Handler>
constexpr Route make_route(Handler&& handler) {
    return {ctre::regex<Path>, std::forward<Handler>(handler)};
}

class Server {
public:
    // 接收编译期确定大小的array
    template<size_t N>
    constexpr Server(const std::array<Route, N>& routes) : m_routes(routes) {}

    void listen() {
        for (const auto& route : m_routes) {
            // 匹配逻辑和之前一样
        }
    }

private:
    const std::array<Route, N> m_routes;
};

// 辅助函数自动推导array大小
template<size_t N>
constexpr auto make_server(const std::array<Route, N>& routes) {
    return Server{N, routes};
}

使用方式也很直观:

int main() {
    // 所有路由都在编译期初始化
    constexpr auto routes = std::array{
        make_route<"/hello">([] { std::cout << "hello\n"; }),
        make_route<"/goodbye">([] { std::cout << "goodbye\n"; })
    };
    auto server = make_server(routes);
    server.listen();
}

为啥你之前的Container类不行?

你之前的Container用了std::vector,哪怕C++20允许在constexpr块里用vector,它的size()还是运行时动态值,没法用来初始化std::array的大小(array的大小必须是编译期常量)。所以要实现编译期列表,必须用模板参数或者constexpr初始化列表这种编译期确定的方式,不能依赖运行时容器。


总结一下:如果你死磕server.get<"/path">(handler)的API风格,方案1的模板元编程是最优解;如果想省点模板代码,方案2的constexpr array足够满足需求,而且代码更易懂。两种方案都能实现编译期生成正则、运行时遍历const数组的目标。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 23:29:11