繁体   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