24 Temmuz 2020 Cuma

if constexpr - C++17 İle Geliyor - Koşul False İse Kodu Derlemez

Giriş
C++17 ile geliyor.

Ne İşe Yarar?
Elimizde şöyle bir kod olsun. Bu klasik bir fibonacci serisi.
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;
};
Bu kodu şöyle çağıralım. C++ durma koşulu olarak < 0 olsa bile tüm kodu derlemek zorunda. Bu yüzden hata veriyor.
std::cout << fibonacci<-1>::value << std::endl;
Hata şöyle. Çok fazla recursive çağrı olduğu için derleyemiyor.
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) static const int value = n < 0 ? 0 : fibonacci<n-1>::value + fibonacci<n-2>::value; | ^~~~~
Açıklaması şöyle
We are dealing with template instantiation. You used fibonacci<n-1>::value, and that requires the complete object type fibonacci<n-1> to be instantiated. The type has to be checked, to see if it has a member value that can be used in such an expression.

Instantiating a class template causes the declarations of its members to be instantiated. The declaration of the static data member contains an initializer, which must therefore be instantiated as well. So we hit the need to instantiate the template recursively.

Simply naming fibonacci<n-1> won't cause it to be instantiated (think forward declarations). If you want to delay the instantiation, you must delay using those types in a way that requires their definition (such as accessing a member).
Bu yüzden eski kodlarda std::conditional_t kullanılarak şöyle yapılır
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; };
Açıklaması şöyle
std::conditional_t will choose a type based on the condition. Then, the ::value of that type (and only that type) is accessed. So nothing is fully instantiated until actually needed.
Ancak bu kodu okumak zor. Bu yüzden C++17 ile if constexpr geliyor. C++17 ile açıklaması şöyle
The only way to avoid instantiation of a template inside an expression is to not compile the expression at all. This allows you to avoid instantiating the templates with incorrect arguments.

You can do this by using if constexpr from C++17:
Bu yüzden şöyle yaparız
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;
    }
  }();
};
Template İçinde
Örnek
Şöyle yaparız.
template <int N>
constexpr int fib() {
  if constexpr (N == 1 || N == 2) {
    return 1;
  } else {
    return fib<N - 1>() + fib<N - 2>();
  }
}
Örnek
Şöyle yaparız. Burada if (x) ifadesi hiçbir zaman derlenmeyecek ve çalışmayacak.
template <typename T>
void f() {
  constexpr T x = -1;
  if constexpr (x >= 0){
    constexpr int y = 1<<x;
  }
}
int main() {
    f<int>();
}
Örnek
Şöyle yaparız
#include <string>
#include <iostream>
#include <type_traits>
#include <map>

void pp(const std::string& str)
{
   std::cout << str << std::endl;
}

template<typename... T>
void pp(const T&... args)
{
   // do something with args!
}

template<typename T>
constexpr void test()
{
  if constexpr (std::is_null_pointer_v<T>)
    pp("x", "x"); // call with args
  else
    pp("x"); // call with string
}
Template Dışında
#if şeklinde macro anlamına gelmez. Açıklaması şöyle. Yani template dışında kullanılırsa kod derlenir.
Outside a template, a discarded statement is fully checked. if constexpr is not a substitute for the #if preprocessing directive:
Örnek
Elimizde şöyle bir kod olsun. Derleme hatası verir.
void f() {
  if constexpr(false) {
    int i = 0;
    int *p = i; // Error even though in discarded statement
  }
}
Örnek
Elimizde şöyle bir kod olsun.
using T = double;

int main()
{
  f();
}
void f() {
  T x = 2;
  if constexpr(std::is_integral_v<T>)
  {
    std::cout << std::min(static_cast<int64_t>(2), x);
  } else {
    std::cout << std::min(1.0, x);
  }
}
Bu kod template olmadığı için derleme hatası verir. Hata şöyle. Sebebiyse std::min() metodunun her iki parametresinin de aynı olması gerekmesi. Yani f() metodu tempate olmadığı için if constexpr() kodun derlenmesini engellemiyor.
<source>:15:57: error: no matching function for call to 'min(int64_t, T&)'
Bu kodu şöyle yaparsak hata düzelir
template <typename T>
void f() {
  T x = 2;
  if constexpr(std::is_integral_v<T>)
  {
    std::cout << std::min(static_cast<int64_t>(2), x);
  } else {
    std::cout << std::min(1.0, x);
  }
}

Hiç yorum yok:

Yorum Gönder