c++ - 循环中的模板元编程?

标签 c++ c++11 templates metaprogramming

几分钟前,我正在练习琐碎的算法问题。下面的代码(算法问题的具体逻辑并不重要,所以我们只需要知道主函数上面的代码只是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这样的技巧是 if 的变电站和loop 。我学到了关于 if 的愚蠢用法的教训来自 this article- Compile Time Loops with C++11 - Creating a Generalized static_for Implementation 的模板编程,这里的代码:

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

作者的解释:

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.

哎呀...,我只是陈述我所知道的...

最后,谈到我的问题:

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

    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<800>::oneDArr[i] + ...应在编译时初始化,因此 800 * 800 实例化应在编译时确定。

Q1:主函数中的运行时循环是否与 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 example修改 main 函数后,输出为 12 (正确答案是128)

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

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

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

Q3:如何解释输出12

更新:

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

Q1 和 Q2 仍然让我困惑

最佳答案

您忘记在 ' for (; j < 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);
}

关于c++ - 循环中的模板元编程?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47460136/

相关文章:

c++ - 是否有任何头文件可以独立于平台读取文本文件?

c++ - 可以具有不同基成员实现的类的最佳实现

c++ - 模板和 g++ 4.7

c++ - 模板特化别名

c++ - 如何使用 static_assert 检查派生类是否覆盖了非虚拟基类方法?

c++ - 模板代码中的转发引用与 const 左值引用

c++ - 无法写入文件中的数据,程序C++没有错误

c++ - 使用 boost::algorithms::join 提取和连接字符串

c++ - 高效计算随机误差

c++ - 使用一个函数对两个不同的 vector (每个 vector 都有 "active"bool)进行排序