【发布时间】: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);
}
我知道 loop 和 if(不包括 constexpr function 和 if constexpr)是运行时,而不是编译时。所以像template specialization 这样的技巧是if 和loop 的变电站。我从this article-
Compile Time Loops with C++11 - Creating a Generalized static_for Implementation 学到了关于模板编程中if 的愚蠢用法的教训,这里是代码:
#include <iostream>
template<int index> void do_stuff()
{
std::cout << index << std::endl;
}
template<int max_index, int index = 0> void stuff_helper()
{
if (index <= max_index)
{
do_stuff<index>();
stuff_helper<max_index, index + 1>();
}
}
int main()
{
stuff_helper<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<800>::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);
}
现在i 和j 必须在运行时确定,因为scanf。我只是将额外的两个0 传递给标准输入。
这里是live examplealter main 函数后,输出是12(正确答案是128)
它编译成功并且没有产生警告。让我困惑的是输出与原始代码不同(live code,其输出为128(等于正确答案)。
dubug后发现关键是改代码后for (; i < 800; i++)只执行一次i = 0,而应该执行1~799,这就是12的原因,而不是128。
Q2:如果在运行时无法确定 for 循环的深度,并且 TMP 代码存在于循环中,会发生什么情况?
Q3:如何解释输出12
更新:
Q3 已经被@Scott Brown 解决了,我太粗心了。
Q1 和 Q2 仍然让我感到困惑
【问题讨论】:
-
您误解了实例化的含义。在这种特殊情况下,它意味着从模板“创建”具体的类/函数。在运行时不会发生这样的事情。循环无关紧要。
-
递归实例化模板函数的“无限循环”之所以存在,只是因为从函数模板实例化的每个函数都需要另一个实例化,因为
if作为运行时构造,不会影响实例化跨度>
标签: c++ c++11 templates metaprogramming