【问题标题】:Template Metaprogramming in loop?循环中的模板元编程?
【发布时间】:2017-11-23 16:45:21
【问题描述】:

几分钟前,我正在练习琐碎的算法问题。下面的代码(算法问题的具体逻辑并不重要,所以我们只需要知道main函数上面的代码就是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);
}

我知道 loopif(不包括 constexpr functionif constexpr)是运行时,而不是编译时。所以像template specialization 这样的技巧是ifloop 的变电站。我从this article- Compile Time Loops with C++11 - Creating a Generalized static_for Implementation 学到了关于模板编程中if 的愚蠢用法的教训,这里是代码:

#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;
}

作者解释:

从表面上看,if 语句可能会负责终止递归,就像它如何与“正常”的基于运行时的递归算法一起工作一样。但这就是问题所在。在运行时有效的在编译时无效。

这是一个无限循环,只有在编译器将自己限制在某个递归深度时才会停止。在 clang 中,我收到一个错误致命错误:递归模板实例化超出最大深度 256。您可以预期您选择的编译器会出现类似错误。

糟糕...,我只是陈述我所知道的...

最后是我的问题:

现在模板的实例化(特别是两次解析)是在编译时进行的。所以最顶层代码中的所有模板实例化都应该在编译时:

    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
            ...
        }
        ...
    }

众所周知 1. 这里的两个for loop 是运行时,虽然它不在模板函数/类的定义中,只是在主函数中。 2.每个auto temp = oneDArrayMaker&lt;800&gt;::oneDArr[i] + ...都应该在编译时初始化,所以800 * 800个实例应该在编译时确定。

Q1:main 函数中的运行时循环是否与 799*799 编译时模板初始化相冲突?

我的假设:在编译时,编译器知道循环的深度,所以只需展开循环,运行时没有循环。 但我认为这两个循环(i 和 j)也不能在运行时确定,我将 main 函数更改为:

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

现在ij 必须在运行时确定,因为scanf。我只是将额外的两个0 传递给标准输入。

这里是live examplealter main 函数后,输出是12(正确答案是128)

它编译成功并且没有产生警告。让我困惑的是输出与原始代码不同(live code,其输出为128(等于正确答案)。

dubug后发现关键是改代码后for (; i &lt; 800; i++)只执行一次i = 0,而应该执行1~799,这就是12的原因,而不是128

Q2:如果在运行时无法确定 for 循环的深度,并且 TMP 代码存在于循环中,会发生什么情况?

Q3:如何解释输出12

更新:

Q3 已经被@Scott Brown 解决了,我太粗心了。

Q1 和 Q2 仍然让我感到困惑

【问题讨论】:

  • 您误解了实例化的含义。在这种特殊情况下,它意味着从模板“创建”具体的类/函数。在运行时不会发生这样的事情。循环无关紧要。
  • 递归实例化模板函数的“无限循环”之所以存在,只是因为从函数模板实例化的每个函数都需要另一个实例化,因为 if 作为运行时构造,不会影响实例化跨度>

标签: c++ c++11 templates metaprogramming


【解决方案1】:

您忘记在“for (; j &lt; 800; j++)”之前重置 j。

int main()
{
    int n{}, ans{}, i{}, j{};
    scanf("%d", &n);
    scanf("%d %d", &i, &j);
    std::cout << n << " " << i << " " << j << std::endl;

    int j_orig = j;// here

    for (; i < 800; i++)
    {

        j = j_orig;// and here

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-06
    • 1970-01-01
    • 2019-05-06
    相关资源
    最近更新 更多