[英]Possible ODR-violations when using a constexpr variable in the definition of an inline function (in C++14)
[英]Possible to instantiate templates using a for loop in a C++14 constexpr function?
我一直在使用 SVN 構建的 clang 來試驗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: no matching function for call to 'foo' sum += foo(); ^~~~~~ test2.cpp:11:15: note: candidate template ignored: invalid explicitly-specified argument for template parameter '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循環思維,例如使用遞歸是可能的。 它更丑陋,但對於某些用戶來說,從運行時(不應在運行時)到編譯時獲得一些繁重的東西可能是值得的。 對於某些人來說,增加的丑陋可能不值得犧牲代碼清潔度,所以這取決於每個人的決定,我只是想表明這是可能的。 對資源有嚴格限制的嵌入式域可能值得考慮。 通過這種遞歸,您可以描述許多算法,您可能可以像 prolog 一樣執行 tail + head 並一次處理一個條目,或者復制數組並在當時更改一次條目(窮人的以模板為中心的串聯而不改變大小返回類型)。 只有一個限制,在編譯時循環結束的“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 )。 而且您還必須在某處結束循環,因此您必須實現單獨的exit case 。 為了節省您額外的輸入,最好將退出案例和特殊案例放在同一索引處,這樣您就不必創建重復的實現並增加維護。 因此,您必須決定以遞增或遞減的方式計數是否更好。 因此,您將只需要實現一次組合的特殊情況和退出情況。 把它放到上下文中,對於像階乘這樣的東西,我會遞減,然后 0 案例和循環結束將使用相同的代碼實現,然后讓算法在從深度遞歸返回時完成工作。
如果您沒有任何特殊情況並且必須創建一個特殊情況,例如在上面的代碼中,我知道返回 0 是安全的,並且我知道您何時數到 10 但不包括它,因此我做了索引 10 處的特殊情況並返回 0。
template<>
constexpr int getSum<maxLoopIndex>() {
return 0;
}
如果這個技巧對你來說是不可能的,那么你必須實現算法的一個子集(沒有遞歸)但在索引 9 處停止,如下所示:
template<>
constexpr int getSum<maxLoopIndex-1>() {
return foo<maxLoopIndex-1>();
}
注意:您可以使用 const 變量和方程,只要它們在編譯時即可。
完整示例:
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];
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.