I am working with a third party library which provides a Numeric
class that must be templated with precision and scale arguments. Unfortunately, within the context of the library I am creating, the precision and scale is not known until runtime, so I need to create a runtime mapping of those values to their respective templated functions.
The Numeric library supports up to 38 decimal digits, so in theory I need to create a mapping of the form:
mapping = {
<38, 38>: []() { // do something with Numeric<38, 38> ... },
<38, 37>: []() { // do something with Numeric<38, 37> ... },
...
<37, 37>: []() { // do something with Numeric<38, 37> ... },
<37, 36>: []() { // do something with Numeric<38, 37> ... },
...
}
For all precision and scale arguments in the range (0, 38], where scale <= precision
This is the code I've created, ignoring the implementation of the std::function map values and using a smaller parameter pack for now:
using funcMapType = std::map<std::pair<int, int>, std::function<void(void)>>;
template <int P, int S, int... Rest> struct NumericCreatorInserter {
static void insert(funcMapType &func_map) {
NumericCreatorInserter<P, P>::insert(func_map);
NumericCreatorInserter<P, S>::insert(func_map);
NumericCreatorInserter<P, Rest...>::insert(func_map);
NumericCreatorInserter<S, Rest...>::insert(func_map);
}
};
template <int Precision, int Scale>
struct NumericCreatorInserter<Precision, Scale> {
static void insert(funcMapType &func_map) {
std::cout << "Precision is: " << Precision << " and scale is: " << Scale
<< std::endl;
func_map.emplace(std::make_pair(Precision, Scale), [](void) { return; });
}
};
static funcMapType initializeNumericCreatorMap() {
funcMapType numeric_creators;
NumericCreatorInserter<3, 2, 1, 0>::insert(numeric_creators);
return numeric_creators;
};
Debugging this at runtime, I get the following:
Precision is: 3 and scale is: 3
Precision is: 3 and scale is: 2
Precision is: 3 and scale is: 3
Precision is: 3 and scale is: 1
Precision is: 3 and scale is: 0
Precision is: 1 and scale is: 0
Precision is: 2 and scale is: 2
Precision is: 2 and scale is: 1
Precision is: 2 and scale is: 0
Precision is: 1 and scale is: 0
I am expecting to see pairs of (3, 3), (3, 2), (3, 1), (3, 0), (2, 2), (2, 1), etc...
I think I understand the issue with my code - the sequence starting with (3, 3), (3, 1), (3, 0), and (1, 0) happens when P = 3, S = 1, and Rest...=0, which occurs during the first recursive invocation of the function
With that said, I am struggling with how to express what I am trying to achieve in C++. Is there another template specialization I should be introducing to get this to work?
Ideally I am looking for a solution that works with C++17
A way to transform runtime value into compile time value is to use std::variant
:
template <std::size_t N>
constexpr auto to_integral_variant(std::size_t n)
{
return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
using ResType = std::variant<std::integral_constant<std::size_t, Is>...>;
ResType all[] = {ResType{std::integral_constant<std::size_t, Is>{}}...};
return all[n];
}(std::make_index_sequence<N>());
}
then, dispatch with std::visit
:
auto foo(std::size_t prec, std::size_t scale)
{
return std::visit([](auto P, auto S) {
return func<P(), S()>();
}, to_integral_variant<MAX>(prec), to_integral_variant<MAX>(scale));
}
To "ensure" that P() <= S()
in your code, you might use if constexpr
:
return std::visit(
[](auto P, auto S) {
if constexpr (S() <= P()) {
return func<P(), S()>();
} else {
throw "unreachable";
}
}, to_integral_variant<MAX>(prec), to_integral_variant<MAX>(scale));
or even group the pair
template <std::size_t N>
struct Helper
{
template <std::size_t P>
struct Inner {
using type = decltype(
[]<std::size_t... Is>(std::index_sequence<Is...>){
return std::tuple<
std::integral_constant<std::pair<std::size_t, std::size_t>,
std::pair(P, Is)>...>();
}(std::make_index_sequence<P + 1>())
);
};
using type = typename tuple_to_variant<
decltype(
[]<std::size_t... Is>(std::index_sequence<Is...>){
return std::tuple_cat(typename Inner<Is>::type{}...);
}(std::make_index_sequence<N>())
)
>::type;
};
template <std::size_t N>
constexpr auto to_integral_pair_variant(std::size_t prec, std::size_t scale)
{
return std::visit(
[](auto P, auto S) -> Helper<N>::type
{
if constexpr (S() <= P()) {
return std::integral_constant<std::pair<std::size_t, std::size_t>,
std::pair(P(), S())>{};
} else {
throw "unreachable";
}
}, to_integral_variant<N>(prec), to_integral_variant<N>(scale)
);
}
and then
auto foo(std::size_t prec, std::size_t scale)
{
return std::visit(
[](auto P) {
return func<P().first, P().second>();
}, to_integral_pair_variant<MAX>(prec, scale));
}