Search code examples
c++algorithmtemplatesc++20non-type-template-parameter

Is there a way to iterate through templated functions/class instances with non-type-template-parameter via std::iota?


If I wanted to invoke instances of a function template where the template arguments are sequential integers, I know a loop like the following won't work:

template <int n>
void foo() {/*...*/}

for (int i = 0; i < 4; ++i)
  foo<i>();

And same with classes.

Judging by the information I've been able to find, there are no tricks to get around this. But I haven't seen anyone trying to use std::iota, and it's constexpr since C++20.

2024 standard says:

13.4.3 Template non-type arguments [temp.arg.nontype]

A template-argument for a non-type template-parameter shall be a converted constant expression (7.7) of the type of the template-parameter.

And I couldn't really understand this 7.7 part.

But as I understand constexpr, it should be known at compile time (like I know what I'm talking about). So maybe something like iterating through std::vector of needed indexes filled with iota could work.


Solution

  • If I wanted to invoke instances of a function template where the template arguments are sequential integers [..]?

    From your requirements, I would rather suggest a solution using std::integer_sequence with range and fold expression expansion. For example, if you have:

    // Example function template
    template <int N> constexpr void foo()
    {
        std::cout << "foo<" << N << "> called\n";
    }
    
    // Example class template
    template <int N> struct Bar
    {
        constexpr void print() const
        {
            std::cout << "Bar<" << N << "> instance\n";
        }
    };
    

    you might create helper functions for each cases as follows (also possible to combine with if constexpr):

    // Helper for function templates with compile-time range
    template <int Start, int End, typename Func>
    constexpr void do_loop(Func)
    {
        // For each element in the sequence, call foo<Is + Start>()
        [] <typename T, T... Is>(std::integer_sequence<T, Is...>) {
            (foo<Is + Start>(), ...);
        }(std::make_integer_sequence<int, End - Start>{});
    }
    
    // Generic helper for member function calls over a compile‐time range.
    // Instead of accepting a pointer‐to‐member function, we accept a callable F,
    // which (given an instance of C<...>) performs the desired call.
    template <template <int> class C, int Start, int End, typename F>
    constexpr void do_loop(F f)
    {
        // Generate an integer sequence and for each index, create an instance and call f on it.
        [f] <typename U, U... Is>(std::integer_sequence<U, Is...>) {
            ((f(C<Is + Start>{})), ...);
        }(std::make_integer_sequence<int, End - Start>{});
    }
    

    You would call it like:

    do_loop<3, 6>(foo<0>);
    do_loop<Bar, 0, 3>([](auto&& obj) { obj.print(); });
    

    Output:

    foo<3> called
    foo<4> called
    foo<5> called
    
    
    Bar<0> instance
    Bar<1> instance
    Bar<2> instance
    

    (See live demo)