Replace std::variant/visit with function pointer table dispatch for faster compilation#426
Open
Replace std::variant/visit with function pointer table dispatch for faster compilation#426
Conversation
The previous approach created two 39-element std::variant types and used std::visit to dispatch on their cartesian product (39² = 1521 combinations per call site, 3 sites total). std::visit is notoriously compile-time heavy due to its internal dispatch table generation and type deduction. Replace with integral_dispatch, which builds a std::array of function pointers for O(1) runtime dispatch without any variant machinery. Each stateless lambda converts directly to a function pointer. The nested two-level dispatch generates the same template instantiations but avoids the compile-time cost of variant construction and visit table generation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR replaces std::variant/std::visit-based runtime-to-compile-time dispatch for Hyper Numeric<P,S> with a function-pointer table dispatcher to significantly reduce compile-time overhead in decimal read/write paths.
Changes:
- Introduce
integral_dispatch<N>innumeric_gen.hppto dispatch a runtime index to a compile-timestd::integral_constant. - Update decimal handling in
reader.cppandwriter.cppto use nestedintegral_dispatchinstead ofstd::visitover two variants. - Remove
<variant>includes now that variant-based dispatch is eliminated.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/pantab/numeric_gen.hpp | Adds integral_dispatch function-pointer table for integral index dispatch. |
| src/pantab/reader.cpp | Replaces std::visit decimal conversion with nested integral_dispatch. |
| src/pantab/writer.cpp | Replaces std::visit decimal insert logic with nested integral_dispatch in two call sites. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+15
to
+23
| constexpr void integral_dispatch(std::size_t n, F &&f) { | ||
| [&]<std::size_t... Is>(std::index_sequence<Is...>) { | ||
| using FnPtr = void (*)(F &); | ||
| const std::array<FnPtr, N> table{ | ||
| {static_cast<FnPtr>([](F &fn) { | ||
| fn(std::integral_constant<std::size_t, Is>{}); | ||
| })...}}; | ||
| table[n](f); | ||
| }(std::make_index_sequence<N>{}); |
Comment on lines
+431
to
+437
| integral_dispatch<PrecisionLimit>(precision_, [&](auto P) { | ||
| integral_dispatch<PrecisionLimit>(scale_, [&](auto S) { | ||
| if constexpr (S() <= P()) { | ||
| InsertNull<hyperapi::Numeric<P(), S()>>(); | ||
| } | ||
| }); | ||
| }); |
Comment on lines
+473
to
+480
| integral_dispatch<PrecisionLimit>(precision_, [&](auto P) { | ||
| integral_dispatch<PrecisionLimit>(scale_, [&](auto S) { | ||
| if constexpr (S() <= P()) { | ||
| const auto value = hyperapi::Numeric<P(), S()>{str}; | ||
| InsertValue(std::move(value)); | ||
| } | ||
| }); | ||
| }); |
Comment on lines
+298
to
+310
| const auto decimal_string = [&]() -> std::string { | ||
| std::string result; | ||
| integral_dispatch<PrecisionLimit>(precision_, [&](auto P) { | ||
| integral_dispatch<PrecisionLimit>(scale_, [&](auto S) { | ||
| if constexpr (S() <= P()) { | ||
| const auto decimal_value = value.get<hyperapi::Numeric<P(), S()>>(); | ||
| auto value_string = decimal_value.toString(); | ||
| std::erase(value_string, '.'); | ||
| return value_string; | ||
| result = decimal_value.toString(); | ||
| std::erase(result, '.'); | ||
| } | ||
| throw "unreachable"; | ||
| }, | ||
| to_integral_variant<PrecisionLimit>(precision_), | ||
| to_integral_variant<PrecisionLimit>(scale_)); | ||
| }); | ||
| }); | ||
| return result; | ||
| }(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The previous approach created two 39-element
std::varianttypes and usedstd::visitto dispatch on their cartesian product (39² = 1521 combinations per call site, 3 sites total).std::visitis notoriously compile-time heavy due to its internal dispatch table generation and type deduction.This replaces it with
integral_dispatch, which builds astd::arrayof function pointers for O(1) runtime dispatch without any variant machinery. Each stateless lambda converts directly to a function pointer. The nested two-level dispatch generates the same template instantiations but avoids the compile-time cost of variant construction and visit table generation.Changes
numeric_gen.hpp: Replaceto_integral_variant(variant + array construction) withintegral_dispatch(function pointer table + direct index)reader.cpp: Convertstd::visitcall to nestedintegral_dispatchcallswriter.cpp: Convert bothstd::visitcalls to nestedintegral_dispatchcalls<variant>includes from all three files