可以在C++14 constexpr函数中使用for循环实例化模板
Possible to instantiate templates using a for loop in a C++14 constexpr function?
我一直在摆弄clang的SVN构建,以试验constexpr
的宽松规则。到目前为止,我还无法确定的一件事是,在编译时,是否可以在constexpr函数中循环遍历元组内的元素。
因为我没有一个兼容C++14的标准库来测试,所以我准备了以下等效的测试:
template<int N>
constexpr int foo() {
return N;
}
constexpr int getSum() {
auto sum = 0;
for (auto i = 0; i < 10; ++i) {
sum += foo<i>();
}
return sum;
}
constexpr auto sum = getSum();
这里有趣的部分是foo<i>()
。在一个非constexpr函数中,我预计它将无法编译,因为您根本无法使用运行时int来生成模板的编译时实例化。但是,因为这是一个constexpr
函数,所以我怀疑这是否可能。特别是,该值在编译时是已知的,即使允许它发生变异。
我知道下面的代码将编译:
constexpr auto nValue = 2;
foo<nValue>();
在SVN clang中,我的第一个例子不是:
test2.cpp:19:12:error:没有用于调用"foo"的匹配函数sum+=foo();^~~~~~test2.cpp:11:15:注意:忽略候选模板:显式指定无效模板参数"N"的参数constexpr int foo(){^
首先,我很难理解这个错误消息的第二部分。除此之外,它是C++14标准强制要求的吗?如果是,有人知道为什么不允许使用这种语法吗?
除此之外,它是C++14标准强制要求的吗?如果是,有人知道为什么不允许使用这种语法吗?
这是因为constexpr
并不是编译时间计算或使用的专属。constexpr
函数就是这样,允许在常量表达式中使用函数(或变量)。除此之外,它们是常规函数。在某些上下文中,如static_assert
或数组大小等,仅在编译时情况下,恰好需要一个常量表达式。
您会在代码中注意到,您循环通过一个变量,但循环通过的变量本身不是constexpr
,因此它不是N
模板实例化中使用的常量表达式。就目前情况来看,这与在C++11:中这样做没有什么不同
constexpr bool f(int x) {
static_assert(x > 10, "..."); // invalid
return true;
}
这显然是无效的,因为正如我前面提到的,您不必在独占编译时情况下使用constexpr
函数。例如,没有什么能阻止你这样做:
constexpr int times_ten(int x) {
return x * 10;
}
int main() {
int a = times_ten(20); // notice, not constexpr
static_assert(times_ten(20) == 200, "...");
static_assert(a == 200, "..."); // doesn't compile
}
这对你来说可能太晚了,但它可能对其他找到这个SO的人有用,所以这里是我的答案。
@Rapptz的答案没有错,但你的问题的答案可能是"是"。是的,for循环不能像讨论的那样使用。然而,您想要在循环上迭代,不必使用for循环心态,例如使用recursion是可能的。它更丑陋,但对于一些用户来说,从运行时(不应该在运行时)到编译时获得一些沉重的东西可能是值得的。对于一些人来说,增加的丑陋可能不值得牺牲代码的清洁度,所以这取决于每个人的决定,我只想证明这是可能的。资源受到严格限制的嵌入式领域可能值得考虑。有了这种递归,你可以描述许多算法,你可能会做尾部+头部的prolog,一次处理一个条目,或者复制数组,一次更改其中的一个条目(穷人以模板为中心的串联,而不改变返回类型的大小)。只是有一个限制,在编译时将看不到循环结束的"if"条件。典型的算法是这样的:
template<int input>
int factorialTemplateSimpler() {
if (input < 2) {
return 1;
} else {
return factorialTemplateSimpler<input-1>() * input;
}
}
不会编译,因为工具链会不断递减,直到它被终止(可能在1000次递归之后)。对于要查看结束状态的模板,您必须这样明确地声明:
https://godbolt.org/z/d4aKMjqx3
template<int N>
constexpr int foo() {
return N;
}
const int maxLoopIndex = 10;
template<int loopIndex>
constexpr int getSum() {
return foo<loopIndex>() + getSum<loopIndex + 1>();
}
template<>
constexpr int getSum<maxLoopIndex>() {
return 0;
}
int main() {
constexpr auto sum = getSum<0>();
return sum;
}
这是你想要的,它用C++14编译,不知道为什么它也用C++11编译。
许多算法都有一个特例来从典型算法中做一些特殊的事情,并且你必须为它做一个单独的实现(因为在实例化时不会看到if)。您还必须在某个地方结束循环,因此您必须实现单独的退出情况。为了节省额外的键入,最好将出口大小写和特例放在同一索引中,这样就不必创建重复的实现并增加维护。因此,你必须决定是递增计数还是递减计数更好。因此,您只需要实现一次组合的特殊情况和退出情况。为了把它放在上下文中,对于factorial之类的东西,我会递减,这样0大小写和循环结束将用相同的代码实现,然后让算法在从深度递归返回时完成工作。
如果你没有任何特殊情况,并且你必须做出一个特殊情况,例如在上面的代码中,我知道返回0是安全的,我知道你什么时候计数到10,但不包括它,所以我在索引10处做出了特殊情况,并返回0。
template<>
constexpr int getSum<maxLoopIndex>() {
return 0;
}
如果这个技巧对你来说不可能,那么你必须实现算法的一个子集(没有递归),但在索引9处停止,如下所示:
template<>
constexpr int getSum<maxLoopIndex-1>() {
return foo<maxLoopIndex-1>();
}
注意:您可以使用常量变量和方程式,只要它们在编译时即可。
完整示例:
https://godbolt.org/z/eMc93MvW8
template<int N>
constexpr int foo() {
return N;
}
const int maxLoopIndex = 10;
template<int loopIndex>
constexpr int getSum() {
return foo<loopIndex>() + getSum<loopIndex + 1>();
}
template<>
constexpr int getSum<maxLoopIndex-1>() {
return foo<maxLoopIndex-1>();
}
int main() {
constexpr auto sum = getSum<0>();
return sum;
}
这里有一个例子,它是递减的,让你更容易(最终情况是0):
https://godbolt.org/z/xfzcGMrcq
template<int N>
constexpr int foo() {
return N;
}
template<int index>
constexpr int getSum() {
return foo<index>() + getSum<index-1>();
}
template<>
constexpr int getSum<0>() {
return foo<0>();
}
int main() {
constexpr auto sum = getSum<10 - 1>(); // loop 0..9
return sum;
}
如果你启用C++20,那么我甚至可以在编译时用指向你的foo实例的函数指针填充一个查找表,只是为了证明在编译时有很多可能性。完整示例:
https://godbolt.org/z/c3febn36v
template<int N>
constexpr int foo() {
return N;
}
const int lookupTableSize = 10;
template <int lookupIndex>
constexpr std::array<int(*)(), lookupTableSize> populateLookupTable() {
auto result = populateLookupTable<lookupIndex - 1>();
result[lookupIndex] = foo<lookupIndex>;
return result;
}
template <>
constexpr std::array<int(*)(), lookupTableSize> populateLookupTable<0>() {
std::array<int(*)(), lookupTableSize> lookupTable;
lookupTable[0] = foo<0>;
return lookupTable;
}
const auto lookupTable = populateLookupTable<lookupTableSize - 1>();
int main() {
return lookupTable[2]();
}
这里的另一个例子是填充cos查找表:
https://godbolt.org/z/ETPvx4nex
#include <cmath>
#include <array>
#include <cstdint>
#include <algorithm>
const int lookupTableSize = 32;
template <int lookupIndex>
constexpr std::array<int8_t, lookupTableSize> populateLookupTable() {
auto previousResult = populateLookupTable<lookupIndex + 1>();
auto pi = acosf(-1);
auto inRadians = (((float)lookupIndex)/lookupTableSize) * 2 * pi;
previousResult[lookupIndex] = 127 * std::clamp(cosf(inRadians), -1.0f, 1.0f);
return previousResult;
}
template <>
constexpr std::array<int8_t, lookupTableSize> populateLookupTable<lookupTableSize>() {
return { 0 };
}
const auto lookupTable = populateLookupTable<0>();
int main() {
return lookupTable[2];
}
- 如何在C++中从两个单独的for循环中添加两个数组
- 为什么我的for循环不能正确获取argv
- 在基于范围的for循环中使用结构化绑定声明
- 通过for循环使用用户输入填充列表
- 使用for循环检查数组中的重复项
- 在for循环中使用auto vs decltype(vec.size())来处理字符串的向量
- 为什么 const std::p air<K,V>& 在 std::map 上基于范围的 for 循环不起作用?
- 正在使用for循环创建QScatterSerie
- Python中的for循环与C++有何不同
- 在更改for循环的第三部分后,未使用for循环结果
- 在 for 循环中查找问题时遇到困难
- 嵌套for循环C++的问题(初学者)
- 如何用for循环在c++中生成单词三角形
- 如何在for循环中包含两个索引值的测试条件
- 带有多个独立参数的C++For循环
- 为什么我的程序在for循环中k=0时返回垃圾值
- 如何通过替换顺序代码的while循环来添加OpenMP for循环
- C++-For循环未执行
- 基于范围的 for 循环:迭代使用一个元素扩展的向量
- C++ 无法在字符数组中使用 for 循环打印字母模式