Doc status: Draft 0.4 Last updated: 2026-04-30 Owner: Sebastiano Merlino Audience: Maintainers, library consumers, distro packagers
- Ubiquitous form: "When then the system shall ."
- Optional elements: [when/while/until/where] , the system shall .
- Style: Clear, atomic, testable, technology-agnostic.
- Vision: A modern, ergonomic C++ HTTP server library that hides its libmicrohttpd backend, fits 2026 C++ idioms, and is safe to use without reading the source.
- Target users / segments: C++ developers embedding an HTTP server (services, tools, test fixtures); distro packagers; downstream library authors.
- Key JTBDs:
- "Add an HTTP endpoint to my service in under 30 lines without subclassing."
- "Compile against the library without my code mysteriously failing because of a build flag."
- "Avoid forcing my callers to transitively pull in
<microhttpd.h>and<pthread.h>."
- North-star metrics:
- Public-header dependencies on backend C types: 0.
- Paired
foo()/no_foo()setters: 0. - Hello-world example LOC: ≤10 (currently ~15 with subclassing).
- Release strategy: Single breaking release as v2.0 with a SOVERSION bump. No deprecation period, no compatibility shims, no migration macro. v2.0 is a clean cutover — the v1.x line is end-of-life on the day v2.0 ships; there is no parallel maintenance branch.
- Build-time stability: Public API surface shall not vary based on build-time feature flags (
HAVE_BAUTH,HAVE_DAUTH,HAVE_GNUTLS,HAVE_WEBSOCKET). - Header hygiene: Public headers shall not include
<microhttpd.h>,<gnutls/gnutls.h>,<pthread.h>, or<sys/socket.h>. - Const correctness: Pure accessors of object state shall be
const. Logical-const lazy caching (e.g. populating a request-scoped cache on first call) is permitted and shall be implemented viamutablestorage or equivalent indirection. Methods that drive or query external mutable state — the libmicrohttpd daemon, OS sockets, the listening event loop — are not subject to this rule even when namedget_*(e.g.webserver::is_running,get_fdset,get_timeout,add_connection). - Hot-path performance: Per-request getters shall not allocate or copy containers; they return
const&orstring_view. - Naming: All public method names shall be snake_case; one canonical verb per concept.
- Documentation: v2.0 ships with a rewritten
READMEand an updated examples set. A shortRELEASE_NOTES.mdsummarizes the API changes for users porting from v1; it is informational, not a compatibility commitment.
Problem / outcome
Public headers leak the libmicrohttpd C backend (MHD_Connection*, MHD_Response*, microhttpd.h), <pthread.h>, and <gnutls/gnutls.h> into every consumer translation unit. This makes the C dependency mandatory for users, slows compile times, and prevents future backend swaps. After this work, consumers can #include <httpserver.hpp> and see only C++ types declared by libhttpserver.
In scope
- Use the PIMPL idiom for
webserver,http_request, andhttp_response: backend state (MHD_Daemon*,MHD_Connection*,MHD_Response*, mutexes, GnuTLS handles) lives in animplstruct defined in a private header. Public headers carry only astd::unique_ptr<impl>. Cost: one extra heap allocation per object on the relevant hot paths; benefit: the public ABI no longer leaks any backend type. - Move
get_raw_response/decorate_response/enqueue_responsevirtuals off the publichttp_response(relocate to a detail base or eliminate). - Remove
microhttpd.h,pthread.h,<sys/socket.h>includes from public headers. - Replace
gnutls_session_t-returning methods onhttp_requestwith high-level accessors (cert DN, fingerprint, etc.) or an opaque handle.
Out of scope
- Replacing libmicrohttpd as the backend.
- Pluggable backends.
EARS Requirements
PRD-HDR-REQ-001When a consumer includes<httpserver.hpp>then the system shall not transitively include<microhttpd.h>.PRD-HDR-REQ-002When a consumer includes<httpserver.hpp>then the system shall not transitively include<pthread.h>or<sys/socket.h>.PRD-HDR-REQ-003When a consumer includes<httpserver.hpp>then the system shall not transitively include<gnutls/gnutls.h>.PRD-HDR-REQ-004Where a public class needs to hold backend state then the system shall hold it via PIMPL (std::unique_ptr<impl>) whoseimpldefinition lives in a private header.http_responseis exempt: it does not hold backend state (theMHD_Response*is created from the response value inside the dispatch path, never carried on the public type), so it remains a non-PIMPL value type.PRD-HDR-REQ-005Whenget_raw_response,decorate_response, orenqueue_responseare referenced by user code then the system shall not provide them as part of the public API.
Acceptance criteria
grep -lE 'microhttpd\.h|pthread\.h|gnutls\.h|sys/socket\.h' src/httpserver/*.hppreturns no results.- A test program containing only
#include <httpserver.hpp>and an emptymain()compiles without-Ito libmicrohttpd headers.
Problem / outcome
Methods like get_user, get_pass, get_digested_user, check_digest_auth, the get_client_cert_* family, and basic_auth() on the builder are gated behind #ifdef HAVE_BAUTH/HAVE_DAUTH/HAVE_GNUTLS/HAVE_WEBSOCKET. Users must mirror the library's build flags or get inscrutable errors. After this work, declarations are stable across configurations; missing features are reported at runtime.
In scope
- Remove
#ifdef HAVE_*guards from public headers. - When a backend is disabled at build time, methods return a documented sentinel (empty
string_view,false, etc.) or throwhttpserver::feature_unavailable.feature_unavailablederives fromstd::runtime_error. - Add
webserver::features()returning astructofboolflags (basic_auth,digest_auth,tls,websocket). The struct form is preferred over a bitmask orstd::set<std::string>because individual fields are discoverable via auto-completion and stable to extend. - Library build configuration remains unchanged (Autoconf can still disable backend code paths).
Out of scope
- Forcing all backends to be present at runtime.
EARS Requirements
PRD-FLG-REQ-001When a public header is parsed then the system shall not gate any declaration onHAVE_BAUTH,HAVE_DAUTH,HAVE_GNUTLS, orHAVE_WEBSOCKET.PRD-FLG-REQ-002When a user calls a feature method whose backend was disabled at build time then the system shall return a documented sentinel value or throwhttpserver::feature_unavailable.PRD-FLG-REQ-003When a user callswebserver::features()then the system shall return astructofboolfields reporting the runtime availability of basic-auth, digest-auth, TLS, and websockets.PRD-FLG-REQ-004If a feature is unavailable and the user invokes it then the error message shall name both the feature and the build flag that controls it.PRD-FLG-REQ-005When the system defineshttpserver::feature_unavailablethen it shall publicly inherit fromstd::runtime_error.
Acceptance criteria
grep -E '#if(def)? HAVE_(BAUTH|DAUTH|GNUTLS|WEBSOCKET)' src/httpserver/*.hppreturns no results.- A consumer compiles the same source against two builds (TLS-on, TLS-off) without source changes.
Problem / outcome
create_webserver has 70+ setters with paired foo()/no_foo() for nearly every boolean (use_ssl/no_ssl, debug/no_debug, pedantic/no_pedantic, basic_auth/no_basic_auth, digest_auth/no_digest_auth, deferred/no_deferred, regex_checking/no_regex_checking, ban_system/no_ban_system, post_process/no_post_process, single_resource/no_single_resource, use_ipv6/no_ipv6, use_dual_stack/no_dual_stack, etc.) — doubling the surface for zero expressive gain. Constants like DEFAULT_WS_PORT, DEFAULT_WS_TIMEOUT, NOT_FOUND_ERROR are exposed as #define macros polluting consumer namespaces. After this work the builder is roughly half its current size, accepts bool arguments, and exposes constants as constexpr.
In scope
- Replace each paired
foo()/no_foo()with a singlefoo(bool = true)setter. - Replace
#defineconstants in public headers withconstexprinhttpserver::constants. - Validate setter inputs at the build step (port range, non-negative thread counts, etc.) and throw on misuse.
Out of scope
- Replacing the builder pattern with a config struct.
EARS Requirements
PRD-CFG-REQ-001When a user calls a boolean configuration setter then the system shall accept aboolargument with defaulttrue.PRD-CFG-REQ-002When a public header defines a constant then the system shall useconstexprinside thehttpservernamespace, not#define.PRD-CFG-REQ-003If a setter receives an out-of-range value (port > 65535, negative threads, etc.) then the system shall throwstd::invalid_argumentwith a descriptive message.PRD-CFG-REQ-004When v2.0 ships thenno_foo()setters shall not exist in the public API.
Acceptance criteria
create_webserver.hppline count reduced by ≥30%.grep -E '^\s*create_webserver& no_' src/httpserver/create_webserver.hppreturns 0.grep -E '^#define\s' src/httpserver/*.hppreturns 0.
Problem / outcome
Today, even the simplest stateless handler forces the user to subclass http_resource, override one of nine render_* virtuals, and pass a raw pointer whose lifetime they manage. The class form is the right shape when state is shared across HTTP methods of the same resource — a per-resource counter, cache, DB handle, or auth context that GET reads and POST mutates. It is overkill for a handler that is stateless or whose state is fixed at construction. There is also a parallel function-handler convention (render_ptr) used for not-found / error / auth handlers — two styles for one job. register_resource further has an opaque bool family parameter for prefix matching. After this work, both registration styles are first-class: lambdas for stateless or capture-stateful handlers, http_resource subclasses for shared mutable state — picked by the shape of the problem, not forced by the API. Smart-pointer ownership replaces the raw pointer.
In scope
- Add
webserver::on_get/on_post/on_put/on_delete/on_patch/on_options/on_headoverloads takingstd::function<http_response(const http_request&)>(handler returnshttp_responseby value; the library moves the returned value into the dispatch path). - Add a generic
webserver::route(http_method, path, handler)taking the same handler signature, for table-driven registration where the method is a runtime value (config-loaded route tables, programmatic registration). The method-specificon_*entry points remain the preferred call-site form;routeis the escape hatch for when the method isn't known statically. register_resourcetakesstd::unique_ptr<http_resource>(move-in ownership) orstd::shared_ptr<http_resource>. The raw-pointer overload is removed.- Replace the
bool familyparameter with named methods (register_prefixvsregister_path). - Update examples: lambda-first for the stateless "hello world" path, a class-based example explicitly demonstrating state shared across
GET/POSTon the same resource.
Out of scope
- Removing the inheritance-based API. Subclassing
http_resourceremains the canonical way to share mutable state across HTTP methods of one resource.
EARS Requirements
PRD-HDL-REQ-001When a user registers a handler then the system shall accept astd::function<http_response(const http_request&)>overload — the handler returnshttp_responseby value.PRD-HDL-REQ-002When a user wants to register a method-specific handler then the system shall provide entry points namedon_get,on_post,on_put,on_delete,on_patch,on_options,on_head.PRD-HDL-REQ-006When a user wants to register a handler with the HTTP method known only at runtime then the system shall provide a genericwebserver::route(http_method, const std::string& path, handler)entry point taking the samehttp_response-by-value handler signature ason_getetc.PRD-HDL-REQ-003When a user passes ownership of anhttp_resourceor awebsocket_handlerthen the system shall acceptstd::unique_ptrandstd::shared_ptroverloads ofregister_resourceandregister_ws_resourcerespectively.PRD-HDL-REQ-004When a user wants prefix matching then the system shall exposeregister_prefix(...)instead of a positionalbool familyparameter.PRD-HDL-REQ-005When v2.0 ships then the raw-pointer overloadsregister_resource(string, http_resource*, bool)andregister_ws_resource(string, websocket_handler*)shall not exist in the public API.
Acceptance criteria
- A "hello world" example compiles with no subclass, no raw pointers, in ≤10 lines including
main().
Problem / outcome
The response hierarchy has eight subclasses (string_response, file_response, iovec_response, pipe_response, deferred_response, empty_response, basic_auth_fail_response, digest_auth_fail_response). http_response itself uses shared_ptr returns when there is no shared ownership, exposes mutable getters that aren't const (get_header calls headers[key] and inserts on miss), and with_header/with_footer/with_cookie look fluent but return void. Cookies and headers are stored in separate maps despite cookies being headers. After this work http_response is a value type with factory functions, const-correct getters, and a true fluent with_* chain.
In scope
http_responseis a sealed value type built via factory functions:http_response::string(...),http_response::file(...),http_response::iovec(...),http_response::pipe(...),http_response::empty(...),http_response::deferred(...),http_response::unauthorized(scheme, realm, ...).- Remove the
*_responsesubclasses entirely. with_header/with_footer/with_cookiereturnhttp_response&.get_header/get_footer/get_cookieareconst, returnstring_view, do not insert on miss.- Handler return type is
http_responseby value. The library moves the response into the dispatch path; nounique_ptrorshared_ptrwrapping is required.
Out of scope
- Changing how deferred/streaming responses work internally.
EARS Requirements
PRD-RSP-REQ-001When a user constructs a response then the system shall provide a factory function returninghttp_responseby value.PRD-RSP-REQ-002When a user callsget_header,get_footer, orget_cookiethen the system shall not modify the response object's state.PRD-RSP-REQ-003When a user callsget_headeron a missing key then the system shall return an emptystring_view, not insert a new entry.PRD-RSP-REQ-004When a user callswith_header,with_footer, orwith_cookiethen the system shall return a reference to*thisto support chaining.PRD-RSP-REQ-005When a user wants to send an authentication failure then the system shall exposehttp_response::unauthorized(scheme, realm, …).PRD-RSP-REQ-006When v2.0 ships thenstring_response,file_response,iovec_response,pipe_response,deferred_response,empty_response,basic_auth_fail_response, anddigest_auth_fail_responseshall not exist in the public API.PRD-RSP-REQ-007When a user returns a response from a handler then the system shall accepthttp_responseby value, with the library moving the value into the dispatch path. Neitherstd::unique_ptr<http_response>norstd::shared_ptr<http_response>shall be required.
Acceptance criteria
get_headeris callable onconst http_response&.auto r = http_response::string("hi").with_header("X-Foo", "bar").with_status(201);compiles and chains.grep -E 'class\s+\w+_response\s*:' src/httpserver/*.hppreturns no public results.
Problem / outcome
http_request::get_args, get_path_pieces, get_files, get_headers return whole maps/vectors by value (some nested). http_resource::is_allowed and get_allowed_methods are non-const despite only reading state. Each http_resource instance allocates a std::map<std::string, bool> of HTTP methods on construction. After this work, hot-path getters return const& or string_view, read methods are const, and method state is a fixed-size bitmask.
In scope
- Change container-returning getters on
http_requestto returnconst ContainerType&. - Make
is_allowed,get_allowed_methodsconst. - Replace
method_statemap with a bitmask over an HTTP-method enum. - Audit
string_viewreturns for dangling-view risk and document lifetime guarantees.
Out of scope
- Changing the move-only identity of
http_request.
EARS Requirements
PRD-REQ-REQ-001When a user callsget_args,get_path_pieces,get_files, orget_headersonhttp_requestthen the system shall return aconst&to internal storage.PRD-REQ-REQ-002When a user callsis_allowedorget_allowed_methodsonhttp_resourcethen the method shall beconst.PRD-REQ-REQ-003When a method's allow/disallow state is queried then the system shall use a fixed-size bitmask over an HTTP-method enum, not astd::map<std::string, bool>.
Acceptance criteria
- A microbenchmark of
req.get_headers()shows ≥10× reduction in per-call cost vs v1. sizeof(http_resource)decreases by at least the cost of an emptystd::map.
Problem / outcome
stop() vs sweet_kill() (two terminate verbs); ban_ip/disallow_ip/allow_ip/unban_ip (four verbs, two concepts); register_resource (object) vs not_found_resource (function) using "resource" for two distinct things; the webserver(const create_webserver&) constructor is // NOLINT(runtime/explicit) non-explicit, allowing surprising implicit conversions. After this work the public API uses one canonical verb per concept and snake_case throughout, with one historical exception: shoutCAST() is preserved as-is — the name is a deliberate nod to the SHOUTcast streaming protocol it implements, and renaming it would obscure that mapping. It is grandfathered into the public API.
In scope
- Rename
sweet_kill→stop_and_wait. - Collapse the ban/allow verbs to the network-flavored pair
block_ip/unblock_ip. Dropban_ip,unban_ip,allow_ip,disallow_ip. - Rename
not_found_resource/method_not_allowed_resource/internal_error_resourcesetters tonot_found_handler/method_not_allowed_handler/internal_error_handler. - Make the
webserver(const create_webserver&)constructorexplicit.
Out of scope
- Renaming top-level types (
webserver,http_request,http_response,http_resource). - Renaming
shoutCAST(preserved as protocol name; see Problem / outcome).
EARS Requirements
PRD-NAM-REQ-001When a user inspects the public API then the system shall use snake_case for all method names, exceptshoutCASTwhich is preserved as a protocol identifier.PRD-NAM-REQ-002When two methods would denote the same concept then the system shall provide exactly one canonical name.PRD-NAM-REQ-003When a function-based handler setter is named then the system shall use the suffix_handler(not_resource).PRD-NAM-REQ-004When a user constructs awebserverfrom acreate_webserverthen the conversion shall beexplicit.PRD-NAM-REQ-005When the system exposes IP access-control verbs then it shall provide exactly the pairblock_ip/unblock_ipand shall not exposeban_ip,unban_ip,allow_ip, ordisallow_ip.
Acceptance criteria
grep -E '[a-z][A-Z]' src/httpserver/*.hppreturns no public method names matching camelCase other thanshoutCAST.- For each pair of synonymous verbs in v1 (
sweet_kill/stop,ban_ip/disallow_ip,allow_ip/unban_ip), only the canonical name survives in v2.0.
Problem / outcome
v1 exposes extension points as one-shot callbacks scattered across create_webserver (log_access, log_error, not_found_handler, method_not_allowed_handler, internal_error_handler, auth_handler, file_cleanup_callback). Each is single-subscriber, fits one phase, and the set has gaps that surface as long-standing open issues:
- #332 (banned-IP error log entry) — no hook fires when
policy_callbackrejects an IP. - #281 (response-aware access log) —
log_accessruns before the response is built; the user cannot record the status, body size, or duration. - #69 (Common-Log-Format access log with
time-taken) — same root cause as #281. - #273 (early 413 on oversize body) — no pre-body hook can short-circuit before the body is read.
- #272 (delayed body processing, partial) — no per-chunk hook.
After this work, users register hooks at named lifecycle phases via webserver::add_hook(phase, callable). Multiple hooks per phase, registration order, short-circuit semantics at the phases where it makes sense. http_resource::add_hook(...) scopes a hook to one resource. The v1 single-slot setters remain as documented aliases that internally register a hook at the equivalent phase, so existing v2.0 code keeps compiling.
In scope
- Public types:
hook_phaseenum (eleven phases — see architecture §4.10),hook_action(pre/post-handler short-circuit token),hook_handle(RAII), one context struct per phase carrying libhttpserver-defined fields only (never MHD types). webserver::add_hook(phase, callable)per-phase overloads returninghook_handle.http_resource::add_hook(phase, callable)per-route variant, restricted to phases that fire after route resolution (before_handler,handler_exception,after_handler,response_sent,request_completed).- Both pre-handler phases (
request_received,body_chunk,before_handler,handler_exception) AND theafter_handlerpost-handler phase can short-circuit by returninghook_action::respond_with(response).response_sent,request_completed,connection_opened,connection_closed,accept_decision,route_resolvedare observation-only. - Execution order: server-wide hooks before per-route hooks within the same phase; within each scope, registration order.
- Exception policy: a throwing hook is caught and routed through DR-009 §5.2.
- Zero-cost when unused: per-phase
std::atomic<bool> any_hooks_flag short-circuits the hot path to a relaxed atomic load + compare-with-zero. - v1 setters (
log_access,not_found_handler,method_not_allowed_handler,internal_error_handler,auth_handler) retained as documented aliases that internally register a hook.
Out of scope
- WebSocket upgrade hook (no
on_websocket_upgradephase in v2.0). - TLS handshake observation hook.
- Body-buffer steal / streaming-body API (#272 is only partially addressed by
body_chunk; the "give me back my string" half needs a separate streaming-body design — v2.1 candidate). - Hook priority parameter; ordering is registration order in v2.0 (additive evolution if needed later).
EARS Requirements
PRD-HOOK-REQ-001When a user callswebserver::add_hook(phase, callable)then the system shall return ahook_handlethat removes the registration onremove()or on destruction (unless the handle isdetached).PRD-HOOK-REQ-002When a hook is registered at a phase then the system shall invoke it at every firing of that phase, in registration order, until removed.PRD-HOOK-REQ-003When a pre-handler hook (request_received,body_chunk,before_handler,handler_exception) returnshook_action::respond_with(r)then the system shall sendrwithout invoking the resource handler or any remaining hooks at that phase.PRD-HOOK-REQ-004When a post-handler hook atafter_handlerreturnshook_action::respond_with(r)then the system shall replace the in-flight response withrand skip remaining hooks at that phase.PRD-HOOK-REQ-005When a hook throws then the system shall route the exception through the DR-009 §5.2 dispatch error path — log vialog_error, invoke the configuredhandler_exceptionhook chain (or theinternal_error_handleralias), never let the exception escape into libmicrohttpd.PRD-HOOK-REQ-006When the user callshttp_resource::add_hook(phase, callable)then the system shall scope that hook to dispatches of that resource only and shall invoke it after all server-wide hooks at the same phase.PRD-HOOK-REQ-007Whenadd_hookis called concurrently with request dispatch then the system shall apply the new hook to subsequent phase firings without disturbing in-flight invocations.PRD-HOOK-REQ-008When no hooks are registered for a phase then the system shall not incur per-firingstd::functioninvocation cost for that phase.PRD-HOOK-REQ-009When the documentation describes the v1-derived setters (log_access,not_found_handler,method_not_allowed_handler,internal_error_handler,auth_handler) then it shall identify each as an alias that internally registers a hook at the equivalent phase and shall name that phase.
Acceptance criteria
- A test program registers two server-wide
response_senthooks and one per-routeresponse_senthook on a registered resource; serves one request; observes three invocations in (server-wide registration order, then per-route) order. - The set of hooks listed in §4.10 closes issues #332 (single
accept_decisionhook), #281 + #69 (singleresponse_senthook with status / bytes / timing context), and #273 (singlerequest_receivedhook returninghook_action::respond_with(http_response::empty().with_status(413))); each closing example fits in a self-containedexamples/*.cppfile. - A microbenchmark (
bench_hook_overhead) shows per-request cost on a server with zero hooks registered is within 2× microbench noise of the pre-hook-system baseline.
- API-HDR →
src/httpserver/*.hpp,src/webserver.cpp,src/http_response.cpp - API-FLG →
src/httpserver/*.hpp,src/webserver.cpp,src/http_request.cpp - API-CFG →
src/httpserver/create_webserver.hpp,src/httpserver/webserver.hpp - API-HDL →
src/httpserver/webserver.hpp,src/httpserver/http_resource.hpp,examples/ - API-RSP →
src/httpserver/http_response.hpp,src/httpserver/*_response.hpp - API-REQ →
src/httpserver/http_request.hpp,src/httpserver/http_resource.hpp - API-NAM →
src/httpserver/webserver.hpp,src/httpserver/http_response.hpp,README.md - API-HOOK →
src/httpserver/hook_phase.hpp,src/httpserver/hook_action.hpp,src/httpserver/hook_handle.hpp,src/httpserver/hook_context.hpp,src/httpserver/webserver.hpp,src/httpserver/http_resource.hpp,src/webserver.cpp
- OQ-001 —
features()shape. Resolved 2026-04-30:structofbools. Discoverable via auto-completion, easy to extend without breaking ABI. Folded into 3.2. - OQ-002 — PIMPL vs forward declarations. Resolved 2026-04-30: full PIMPL on
webserver,http_request,http_response. Accepting one heap allocation per object as the cost of buying a clean, backend-agnostic public ABI. Folded into 3.1. - OQ-004 — ban/allow verb collapse. Resolved 2026-04-30:
block_ip/unblock_ip. Network-flavored, symmetric, no existing-API inertia worth preserving. Folded into 3.7. - OQ-005 — drop
shoutCAST? Resolved 2026-04-30: keepshoutCASTas-is. The name maps to the SHOUTcast streaming protocol it implements; renaming toshoutcastwould obscure that. Grandfathered as the only camelCase identifier in the public API. Folded into 3.7. - OQ-006 —
feature_unavailablebase class. Resolved 2026-04-30: derives fromstd::runtime_error. Standard, integrates with existing exception-handling code, no need for a library-specific base. Folded into 3.2. - OQ-007 — v1.x maintenance branch? Resolved 2026-04-30: no maintenance branch. v2.0 is a hard cutover; v1.x is end-of-life on the day v2.0 ships. Folded into §1.
- OQ-003 — generic
route(method, path, handler)alongsideon_get/on_post/...? Resolved 2026-04-30: ship both.on_*is the preferred call-site form (clearer when the method is known statically);routeis the escape hatch for table-driven registration where the method is a runtime value. The cost of carrying one extra entry point is small; the cost of forcing every table-driven user to write a 7-armswitchis paid forever. Folded into 3.4.
(none)