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

萝らか妹 提交于 2019-12-21 03:32:44

问题


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.


回答1:


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




回答2:


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.




回答3:


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>{});
 }



回答4:


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>{}(); }



回答5:


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.



来源:https://stackoverflow.com/questions/59200748/how-to-have-a-const-variable-in-a-for-loop-for-the-generation-of-template-classe

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!