簡體   English   中英

可以在 C++14 constexpr 函數中使用 for 循環實例化模板嗎?

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM