簡體   English   中英

C++17 static模板懶評估

[英]C++17 static template lazy evaluation

考慮以下示例:

#include<iostream>

template<int n>
struct fibonacci {
    static const int value = n < 0 ? 0 : fibonacci<n-1>::value + fibonacci<n-2>::value;
};

template<>
struct fibonacci<1> {
    static const int value = 1;
};

template<>
struct fibonacci<0> {
    static const int value = 0;
};

int main() {
    
    std::cout << fibonacci<-1>::value << std::endl;

    return 0;
}

我熟悉 C++ 中的惰性求值,並且希望當傳遞 < 0 的參數時,不會對通用斐波那契模板的 if 語句的第二個分支進行求值。 但是,編譯代碼仍會導致該分支出現無限循環:

Fibonacci.cpp: In instantiation of ‘const int fibonacci<-900>::value’:
Fibonacci.cpp:5:58:   recursively required from ‘const int fibonacci<-2>::value’
Fibonacci.cpp:5:58:   required from ‘const int fibonacci<-1>::value’
Fibonacci.cpp:20:33:   required from here
Fibonacci.cpp:5:58: fatal error: template instantiation depth exceeds maximum of 900 (use ‘-ftemplate-depth=’ to increase the maximum)
    5 |     static const int value = n < 0 ? 0 : fibonacci<n-1>::value + fibonacci<n-2>::value;
      |                                                          ^~~~~

為什么會這樣? 它是否特定於與模板結構相關的內容?

我熟悉 C++ 中的惰性求值,並且希望當傳遞 < 0 的參數時,不會對通用斐波那契模板的 if 語句的第二個分支進行求值。

它不需要評估。 但我們在這里不處理評估。 我們正在處理模板實例化。 您使用fibonacci<n-1>::value ,這需要實例化完整的 object 類型fibonacci<n-1> 必須檢查類型,以查看它是否具有可用於此類表達式的成員value

實例化 class 模板會導致其成員的聲明被實例化。 static 數據成員的聲明包含一個初始化程序,因此也必須對其進行實例化。 所以我們遇到了遞歸實例化模板的需要。

簡單地命名fibonacci<n-1>不會導致它被實例化(想想前向聲明)。 如果您想延遲實例化,您必須以需要它們定義的方式延遲使用這些類型(例如訪問成員)。

舊的元編程技巧(非常符合函數式編程)涉及幫助模板。

template<class L, class R>
struct add {
    static constexpr auto value = L::value + R::value;
};

template<int n>
struct fibonacci {
    static const int value = std::conditional_t<(n < 0), fibonacci<0>, add<fibonacci<n-1>, fibonacci<n-2>>>::value;
};

std::conditional_t將根據條件選擇類型。 然后,訪問該類型(並且僅該類型)的::value 因此,在實際需要之前,沒有任何東西是完全實例化的。

您可以使用if constexpr

template<int n>
struct fibonacci {
    static const int value = []() {
        if constexpr (n < 0) {
            return 0;
        } else {
            return fibonacci<n-1>::value + fibonacci<n-2>::value;
        }
    }();
};

fibonacci使用n的某個值進行實例化時,該實例化中使用的所有表達式也必須被編譯。 這意味着任何使用的模板也必須被實例化。 即使從未計算過包含模板實例化的表達式,這也是必要的。

避免在表達式中實例化模板的唯一方法是根本不編譯表達式。 這允許您避免使用不正確的 arguments 實例化模板。

您可以使用 C++17 中的if constexpr來執行此操作:

template<int n>
struct fibonacci {
    static const int value = [] {
      if constexpr (n < 0) return 0;
       else return fibonacci<n-1>::value + fibonacci<n-2>::value;
    }();
};

這是一個演示

ArtefactoStory Teller-Unslander Monica已經提供了正確的答案。

我只是想在cppinsights的幫助下進一步解釋

出於解釋目的,請考慮稍加修改的原始代碼:

...

template<int n>
struct fibonacci {
    static const int value = n > 0 ? 0 : fibonacci<n-1>::value + fibonacci<n-2>::value;
};

...
int main() {
    
    std::cout << fibonacci<3>::value << std::endl; // modified parameter from -1 to 3 to avoid compilation failure by recursive instantiation

    return 0;
}

這是編譯器生成的樣子

...
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct fibonacci<3>
{
  static const int value = 3 > 0 ? 0 : fibonacci<2>::value + fibonacci<1>::value;
};

#endif


/* First instantiated from: insights.cpp:5 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct fibonacci<2>
{
  static const int value = 2 > 0 ? 0 : fibonacci<1>::value + fibonacci<0>::value;
};

#endif
...
int main()
{
  std::cout.operator<<(fibonacci<2>::value).operator<<(std::endl);
  return 0;
}

從上面生成的代碼可以清楚地看出,即使表達式3 > 0為真,編譯器仍會在n = 2n = 1false表達式中實例化模板

現在看一下Artefacto共享的if constexpr的代碼。 IE

template<int n>
struct fibonacci {
    static const int value = []() {
        if constexpr (n < 0) {
            return 0;
        } else {
            return fibonacci<n-1>::value + fibonacci<n-2>::value;
        }
    }();
};

將模板參數視為-1 這是編譯器將如何解釋它

#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct fibonacci<-1>
{
      
  class __lambda_5_30
  {
    public: 
    inline /*constexpr */ int operator()() const
    {
      if constexpr(-1 < 0) {
        return 0;
      }
      
    }
    
    using retType_5_30 = int (*)();
    inline /*constexpr */ operator retType_5_30 () const noexcept
    {
      return __invoke;
    };
    
    private: 
    static inline int __invoke()
    {
      if constexpr(-1 < 0) {
        return 0;
      }
      
    }
    
    
  } __lambda_5_30{};
  
  static const int value = __lambda_5_30.operator()();
  
  class __anon_5_30;
};

#endif

從上面的代碼可以清楚地看出,編譯器甚至沒有考慮if constexpr的 else 部分。 這就是為什么這段代碼可以完全正常工作的原因。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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