Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 646
NSK Avatar asked Dec 05 '19 17:12

NSK


4 Answers

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

like image 191
Guillaume Racicot Avatar answered Oct 29 '22 14:10

Guillaume Racicot


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 c++17, 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.

like image 20
JeJo Avatar answered Oct 29 '22 14:10

JeJo


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>{});
 }
like image 42
max66 Avatar answered Oct 29 '22 15:10

max66


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>{}(); }
like image 28
Ayxan Haqverdili Avatar answered Oct 29 '22 15:10

Ayxan Haqverdili