Search code examples
c++templatestemplate-argument-deductiontype-deduction

How to remove redundancy from and add flexibility to template parameters?


I want to use constexpr, compile time generated std::arrays for fast value-lookup instead of lengthy runtime calculations. For that I drafted a templated constexprfunction that will be executed at compile time.

Please see the following example code, which allows for ultrafast access to Triangle and Fibonacci numbers and factorials.

#include <iostream>
#include <utility>
#include <array>

constexpr size_t ArraySize = 20u;

// Some generator functions -------------------------------------------------------------
constexpr size_t getTriangleNumber(size_t row) noexcept {
    size_t sum{};
    for (size_t i{ 1u }; i <= row; i++) sum += i;
    return sum;
}
constexpr unsigned long long getFibonacciNumber(size_t index) noexcept {
    unsigned long long f1{ 0ull }, f2{ 1ull }, f3{};
    while (index--) { f3 = f2 + f1; f1 = f2; f2 = f3; }
    return f2;
}
constexpr unsigned long long getFactorial(size_t index) noexcept {
    unsigned long long result{ 1 }; 
    while (index > 0) { result *= index; --index; }
    return result;
}

// Generate a std::array with n elements of a given type and a generator function -------
template <typename DataType, DataType(*generator)(size_t), size_t... ManyIndices>
constexpr auto generateArray(std::integer_sequence<size_t, ManyIndices...>) noexcept {
    return std::array<DataType, sizeof...(ManyIndices)>{ { generator(ManyIndices)... } };
}

// The arrays ---------------------------------------------------------------------------
constexpr auto TriangleNumber = generateArray<size_t, getTriangleNumber>(std::make_integer_sequence<size_t, ArraySize>());
constexpr auto FibonacciNumber = generateArray<unsigned long long, getFibonacciNumber>(std::make_integer_sequence<size_t, ArraySize>());
constexpr auto Factorial = generateArray<unsigned long long, getFactorial>(std::make_integer_sequence<size_t, ArraySize>());

// Some debug test driver code
int main() {
    for (const auto t : TriangleNumber) std::cout << t << ' '; std::cout << '\n';
    for (const auto f : FibonacciNumber) std::cout << f << ' '; std::cout << '\n';
    for (const auto f : Factorial) std::cout << f << ' '; std::cout << '\n';
    return 0;
}

As you can see. The template uses a parameter "DataType". In my opinion this is redundant. This is always the return type of the generator function. And it will also determine the data type for the std::array

So, how can we eliminate this redundancy and just use the type given by the generator function?

Additionally. The functions parameter is always size_t. There is also a redundancy and it is also not very flexible. The type of "ManyIndices" and the function parameter are always the same. So, no need to write that double.

Regarding flexibility. If I want to use a generator function with a different parameter data type, say, unsigned long long as in

constexpr unsigned long long factorial(unsigned long long n) noexcept {
    return n == 0ull ? 1ull : n * factorial(n - 1ull);
}

I cannot do that. So, basically everything should be deduced from the generators functions signature.

This is also valid for the lines like

constexpr auto Factorial = generateArray<unsigned long long, getFactorial>(std::make_integer_sequence<size_t, ArraySize>());

Here, size_t is also the type of the parameter of the given function.


So, how eliminate redundancy and add flexibility?


Solution

  • DataType can be deduced from passed generator, use std::declval.

    std::integer_sequence can be replaced by std::index_sequence.

    Size for calculation must be provided explicitly.

    template <typename GEN, size_t ... Indices>
    constexpr auto generateArray2Helper(GEN gen, std::index_sequence<Indices...>) {
        return std::array<decltype(std::declval<GEN>()(size_t{})), sizeof...(Indices)>{ gen(Indices)... };
    }
    
    template <size_t N, typename GEN>
    constexpr auto generateArray2(GEN gen) {
        return  generateArray2Helper(gen, std::make_index_sequence<N>());
    }
    
    // The arrays ---------------------------------------------------------------------------
    constexpr auto TriangleNumber = generateArray2<ArraySize>(getTriangleNumber);
    constexpr auto FibonacciNumber = generateArray2<ArraySize>(getFibonacciNumber);
    constexpr auto Factorial = generateArray2<ArraySize>(getFactorial);
    

    Demo