jeudi 23 novembre 2017

Template Metaprogramming in loop?

Minutes ago, I was pracitise trival algorithm problem. The codes below(concrete logic of the algorithm problem is not importnant, so all we need to know is codes above main function are just TMP):

#include <array>
#include <algorithm>
#include <iterator>
#include <iostream>

constexpr int digit_in_ones[10] = { 6, 2, 5, 5, 4, 5, 6, 3, 7, 6 };
constexpr int createOneD(int index);

template<int ...>
struct seq
{

};

template<int A, int ...B>
struct gens : gens<A - 1, A - 1, B...>
{

};

template<int ...S>
struct gens<0, S ...>
{
    typedef seq<S...> type;
};

template<int N>
class oneDArrayMaker
{
private:
    typedef typename gens<N>::type sequence;
    template<int ...S>
    static constexpr std::array<int, N> make(seq<S ...>)
    {
        return std::array<int, N>{ {createOneD(S)...}};
    }
public:
    static constexpr std::array<int, N> oneDArr = make(sequence());
};
template<int N>
constexpr std::array<int, N> oneDArrayMaker<N>::oneDArr;

constexpr int createOneD(int index)
{
    return index < 10 ? 
        digit_in_ones[index] : 
        digit_in_ones[(index % 100) / 10] + digit_in_ones[index % 10] + 
        (index >= 100 ? digit_in_ones[index / 100] : 0);
}
int main()
{
    int n{}, ans{};
    scanf("%d", &n);
    for (int i = 0; i < 800; i++)
    {
        for (int j = 0; j < 800; j++)
        {
            auto temp = oneDArrayMaker<800>::oneDArr[i] + oneDArrayMaker<800>::oneDArr[j] + (i+j < 800 ? oneDArrayMaker<800>::oneDArr[i+j] : 100) + 4;
            if (temp == n)
            {
                ans++;
            }
        }
    }
    printf("%d", ans);
}

I knew loop and if(exclude constexpr function and if constexpr) are run-time, not compile time. So tricks like template specialization are substations for if and loop. I were learned a lesson about silly usage of if in template programming from this article- Compile Time Loops with C++11 - Creating a Generalized static_for Implementation, here the codes:

#include &lt;iostream>
template&lt;int index> void do_stuff()
{
    std::cout &lt;&lt; index &lt;&lt; std::endl;
}
template&lt;int max_index, int index = 0> void stuff_helper()
{
    if (index &lt;= max_index)
    {
        do_stuff&lt;index>();
        stuff_helper&lt;max_index, index + 1>();
    }
}
int main()
{
    stuff_helper&lt;100>();
    return 0;
}

author's explanation:

On the surface, it could look like the if statement would be responsible for terminating the recursion, like how this would work with a "normal" run-time based recursion algorithm. But that's the problem. What works at runtime doesn't work at compile time.

This is an infinite loop, and only stops because compilers limit themselves to a certain recursion depth. In clang, I get an error fatal error: recursive template instantiation exceeded maximum depth of 256. You can expect a similar error with your compiler of choice.

Oops..., I just state what I have known...

Finally, it comes to my question:

Now that templates's instantiation(specifically, two-parses) is at compile time. So all templates instantiation in the toppest codes should be at compile time:

    for (int i = 0; i < 800; i++)
    {
        for (int j = 0; j < 800; j++)
        {
            auto temp = oneDArrayMaker<800>::oneDArr[i] + ... // 800 * 800 instantiations should be deternimated at compile time
            ...
        }
        ...
    }

As we known 1. the two for loop here is runtime ahthough it is out of template function/class's definition and just in main function. 2. every auto temp = oneDArrayMaker<800>::oneDArr[i] + ... should be initializated at compile time, so 800 * 800 instantiations should be deternimated at compile time.

Q1: Is runtime loop in main function confliced with 799*799 compile-time template initializations?

My assumption: At compile time, compiler know the depth of the loop, so just unroll the loops, which there is no loop at runtime. But I maintain that the two loops(i and j) can also not be deternimated at runtime, I change main function to:

int main()
{
    int n{}, ans{}, i{}, j{};
    scanf("%d", &n);
    scanf("%d %d", &i, &j);
    std::cout << n << " " << i << " " << j << std::endl;
    for (; i < 800; i++)
    {
        for (; j < 800; j++)
        {
            auto temp = oneDArrayMaker<800>::oneDArr[i] + oneDArrayMaker<800>::oneDArr[j] + (i+j < 800 ? oneDArrayMaker<800>::oneDArr[i+j] : 100) + 4;
            if (temp == n)
            {
                ans++;
            }
        }
    }
    printf("%d", ans);
}

Now i and j have to be deternimated at runtime because of scanf. I just pass extra two 0 to stdin.

Here is live example after alter main function, and output is 12(the right answer is 128)

It compile successfully and no warning is generated. What confuses me is the output is different from the original codes(live code, whose output is 128(equal to the rigth answer).

After dubug, I find the key is after altering codes, for (; i < 800; i++) is only excuate once i = 0, whereas it should have excauted 1~799, that's the reason for 12, not 128.

Q2: If depth of for loop cannot be deternimated at runtime and TMP codes live in loops, what will happen?

Q3: How to explain the output 12

Aucun commentaire:

Enregistrer un commentaire