How to have a const variable in a for loop for the generation of template classes?
Asked Answered
M

5

16

I have a code like

template <size_t N>
class A
{
    template <size_t N>
    someFunctions() {};
};

Now I want to create instances of the class and call the functions in it in a for loop for a set of many values like

// in main()

int main()
{
    for (int i = 1; i <= 100; i++)
    {
        const int N = i;  // dont know how to do this
        A<N> a;
        a.functionCalls();
    }
}

How to do this? Hoping for a method to do this.

Mccalla answered 5/12, 2019 at 17:52 Comment(3)
To be used as a template parameter N needs to be constexpr which if it is a loop variable that's not the caseDear
You can't, does A really need to be a template?Vocable
Yeah there is a need for the class A to be template for some reasons and it is a model of something so it has to be a template classMccalla
W
12

This would require something called a template for which is the expected form expansion statements will take, which is something that look like a for loop but in reality is a templated block in a function that is instanciated multiple times.

Of course, there is a workaround. We can abuse generic lambdas to declare some sort of local templated block and instanciate it ourself:

template <typename T, T... S, typename F>
constexpr void for_sequence(std::integer_sequence<T, S...>, F f) {
    (static_cast<void>(f(std::integral_constant<T, S>{})), ...);
}

This function takes an integer sequence and instantiate the lambda F as many time as the length of the sequence.

It is used like this:

for_sequence(std::make_index_sequence<100>(), [](auto N) { /* N is from 0 to 99 */
  A<N + 1> a; /* N + 1 is from 1 to 100 */
  a.functionCalls();
});

Here, N can be sent as template parameter because it's an object that has a constexpr conversion operator to an integer type. More precisely, it's a std::integral_constant with an increasing value.

Live example

Widmer answered 5/12, 2019 at 18:5 Comment(6)
Ugh. When I see template fun like this, I just know I am going to have to debug it later without a callstack and have to guess what is going on... :)Smart
What is the purpose of static_cast<void> ?Postliminy
@Ayxan avoid problems when the lambda f returns a type that overloads the comma operatorWidmer
@MichaelDorgan This is why we need template for. Abusing language constructs like this is always more painfulWidmer
@GuillaumeRacicot or we need better abstractions than templates for meta programming.Salutary
@AjayBrahmakshatriya the main use for template for is to enable constexpr type metaprogramming via reflection. It replace template metaprogramming and do more. But for that we need basic utilities such as a for loop that can exands at compile time.Widmer
T
6

The N needs to be compile-time constant, which is with a normal for loop is not possible.

But, there are many workarounds. For instance, inspired by this SO post, you can do something like the following. (See a Live demo)

template<size_t N>
class A
{
public:
    // make the member function public so that you can call with its instance
    void someFunctions()
    {
        std::cout << N << "\n";
    };
};

template<int N> struct AGenerator
{
    static void generate()
    {
        AGenerator<N - 1>::generate();
        A<N> a;
        a.someFunctions();
    }
};

template<> struct AGenerator<1>
{
    static void generate()
    {
        A<1> a;
        a.someFunctions();
    }
};

int main()
{
    // call the static member for constructing 100 A objects
    AGenerator<100>::generate();
}

Prints 1 to 100


In , the above can be reduced to a single template AGenerator class(i.e. specialization can be avoided), using if constexpr. (See a Live demo)

template<std::size_t N>
struct AGenerator final
{
    static constexpr void generate() noexcept
    {
        if constexpr (N == 1)
        {
            A<N> a;
            a.someFunctions();
            // .. do something more with `a`
        }
        else
        {
            AGenerator<N - 1>::generate();
            A<N> a;
            a.someFunctions();
            // .. do something more with `a`
        }
    }
};

Output:

1
2
3
4
5
6
7
8
9
10

In case of providing the range of iteration, you could use the following.(See a Live demo)

template<std::size_t MAX, std::size_t MIN = 1> // `MIN` is set to 1 by default
struct AGenerator final
{
    static constexpr void generate() noexcept
    {
        if constexpr (MIN == 1)
        {
            A<MIN> a;
            a.someFunctions();
            // .. do something more with `a`
            AGenerator<MAX, MIN + 1>::generate();
        }
        else if constexpr (MIN != 1 && MIN <= MAX)
        {
            A<MIN> a;
            a.someFunctions();
            // .. do something more with `a`
            AGenerator<MAX, MIN + 1>::generate();
        }
    }
};

int main()
{
    // provide the `MAX` count of looping. `MIN` is set to 1 by default
    AGenerator<10>::generate();
}

Outputs the same as the above version.

Ticklish answered 5/12, 2019 at 18:11 Comment(0)
O
4

From C++20, you can use template lambdas, so you can try something as follows

[]<int ... Is>(std::integer_sequence<int, Is...>)
 { (A<Is+1>{}.functionCall(), ...); }
   (std::make_integer_sequence<int, 100>{});

The following is a full compiling example that print all numbers from 0 to 99

#include <utility>
#include <iostream>

int main()
 {
  []<int ... Is>(std::integer_sequence<int, Is...>)
   { (std::cout << Is << std::endl, ...); }
     (std::make_integer_sequence<int, 100>{});
 }
Opium answered 5/12, 2019 at 18:49 Comment(0)
P
1

One way you can do this is with template meta-programming with something like this:

#include <iostream>

template <std::size_t N>
struct A {
  void foo() { std::cout << N << '\n'; }
};

template <std::size_t from, std::size_t to>
struct call_foo {
  void operator()() {
    if constexpr (from != to) {
      A<from + 1>{}.foo();
      call_foo<from + 1, to>{}();
    }
  }
};

int main() { call_foo<0, 100>{}(); }
Postliminy answered 5/12, 2019 at 18:4 Comment(0)
M
0

Just fo completeness - is it really required for the class or function be templated, if the only usage of function is to be called from loop?

If so and you don't want to write by hand take look at boost.hana.

Medarda answered 17/12, 2019 at 9:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.