7 Şubat 2018 Çarşamba

SFINAE

SFINAE alternatifleri
SFINAE yerine tag dispatching yapılabilir. Bu yöntem STL içinde çokça kullanılıyor. If/Else yazmadan doğru kodun çağrılmasını sağlar.

SFINAE Metod Overloading Seçimi İçin Kullanılır
SFINAE metod overload'ları için kullanılır. Class Overloading yapılamaz. Yani şöyle yapamayız.
template <class T, typename std::enable_if<...>::type >
class Calc
{
};

template <class T, typename std::enable_if<... >>
class Calc
{
};
C++11 ile SFINAE
SFINAE kullanırken template'in parametresi ya tam tipi belirtmeli ya da default bir değer içermeli. Bu parametre genelde sonuncu parametredir.
template<typename T, typename Enable = void>
struct RefTypeTraits {...}
Metodun Dönüş Tipi
SFINAE ve Return Type yazısına taşıdım.

T Parametre Tipine Göre
SFINAE - T Parametre Tipine Göre yazısına taşıdım.

T Parametresinin Metod imzasına göre
Parametre olarak geçiken T nesnesinin bir metodu çağrılacaktır. Çağrılacak metodun olup olmaması, kaç tane parametre aldığı, parametre tipi gibi şeylere göre kodlamak içindir.

Örnek
Parametreye ait foo metodunun int alıp almamasına göre kodu çalıştırmak için şöyle yaparız.
template <typename FooPolicy>
struct Alg {
  template <typename T=FooPolicy> //put FooPolicy in immediate context
  //SFINAEd out if that call is not valid
  auto foo() -> decltype(T::foo(),void()) {
    FooPolicy::foo();
  }

  template <typename T=FooPolicy>
  auto foo() -> decltype(T::foo(0),void()) {
    FooPolicy::foo(6);
  }
};
Örnek
over isimli metodu çağırırken F metodu verilir. F parametresi iki parametre alıyorsa e ve index ile çağrılır. Tek parametre alıyorsa sadece e ile çağrılır. Şöyle yaparız.
template <class Vector, class F>
std::enable_if_t<std::is_invocable<F,
                                   typename Vector::value_type,
                                   std::size_t>::value>
over(const Vector& vec, F&& f)
{
    const auto size = vec.size();
    for (std::size_t i = 0; i < size; ++i) {
        f(vec[i], i);
    }
}

template <class Vector, class F>
std::enable_if_t<std::is_invocable<F, typename Vector::value_type>::value>
over(const Vector& vec, F&& f)
{
    const auto size = vec.size();
    for (const auto& e : vec) {
        f(e);
    }
}

T Parametresinin Metodunun Olup Olmamasına Göre
Parametre olarak geçiken T nesnesinin bir metodunun olup olmamasına göre kodlamak içindir.
Örnek
Elimizde şu kod olsun
struct A { int size() const { return 42; } };
struct B { int getSize() const { return 42; } };
struct C { int GetLength() const { return 42; } };
Şöyle yaparız.
template <typename T>
auto size(const T& x) -> decltype(x.size()) { return x.size(); }

template <typename T>
auto size(const T& x) -> decltype(x.getSize()) { return x.getSize(); }

template <typename T>
auto size(const T& x) -> decltype(x.GetLength()) { return x.GetLength(); }
Kullanmak için şöyle yaparız.
int main()
{
    size(A{});
    size(B{});
    size(C{});
}
Örnek
Şöyle yaparız. Eğer parametrenin find metodunu varsa onu kullanır. Yoksa std::find kullanılır.
Eğer c'nin find metodu varsa ilk find_impl overload tercih edilir. Çünkü son parametre olan 0 int'e daha kolay çevrilir.
Eğer c'nin find metodu yoksa ilk overload erişilemez. Bu durumda ikinci overload kullanılır.
template<class Container, class T>
auto find_impl(Container& c, const T& value, int) -> decltype(c.find(value)){
  return c.find(value);
}

template<class Container, class T>
auto find_impl(Container& c, const T& value, long) -> decltype(std::begin(c)){
  return std::find(std::begin(c), std::end(c), value);
}

template<class Container, class T>
auto find(Container& c, const T& value) -> decltype(find_impl(c, value, 0)) {
  return find_impl(c, value, 0);
}
Aynı şeyi şöyle de yapabilirdik.
template <class C, class V>
auto generic_find_impl(C const& container, V const& value, int /* unused */)
    -> decltype(container.find(value))
{
 return container.find(value);
}

template <class C, class V>
auto generic_find_impl(C const& container, V const& value, ...)
{
  using std::begin;
  using std::end;
  return std::find(begin(container), end(container), value);
}

template <class C, class V>
bool generic_find(C const& container, V const& value) {
  using std::end;
  return generic_find_impl(container, value, 0) != end(container);
}
Örnek
T Parametresi metoda bir şekilde tanıtılmalı. Açıklaması şöyle
SFINAE only happens with immediate context. Since T is a template argument of the class
and not the template argument of the function, it is not immediate context. That means it
becomes a "hard" error. It's a hard error because no matter what argument you send to the
template argument of the constructor, it will always be an error.
Elimizde şu kod olsun.
#include <iostream>

using namespace std;

template <typename T>
class test {
public:
    T value;

    template <typename... Args, typename = decltype(T())>
    test(Args... args): value(args...)
    {
       cout <<"ctor running\n";
    }

    template <typename... Args>
    test(Args...) : value(1)
    {
       cout <<"ctor unspec  running\n";
    }
};


class t
{
public:
    t() = delete;
    explicit t(int) {}
};


int main()
{
    test<t> h;
}
Bu kod derlenmez.
error: call of overloaded 'test()' is ambiguous
     test<t> h;
             ^
candidate: 'test<T>::test(Args ...) [with Args = {}; T = t]'
     test(Args... args)
     ^~~~
candidate: 'test<T>::test(Args ...) [with Args = {}; <template-parameter-2-2> = t; T = t]'
     test(Args... args): value(args...)
     ^~~~
T parametresini metoda getirmek ve t sınıfının default constructor'ı olmadığı için ikinci metodu seçtirmek için şöyle yaparız.
template <typename... Args, typename U = T, typename = decltype(U{})>
test(Args... args): value(args...)
{
   cout <<"ctor running\n";
}
Aynı şeyi şöyle yaparız.
template <typename... Args, typename U = T,
    std::enable_if_t<!std::is_default_constructible<U>::value>* = nullptr>
test(Args...) : value(1)
{
   cout <<"ctor unspec  running\n";
}





Hiç yorum yok:

Yorum Gönder