C++11 – Derleme zamanında tür belirleyiciler (type traits)

Basitçe derleme zamanında tür hakkında bilgi edinmemizi sağlarlar. Kullanımları oldukça basittir:


#include <iostream>
#include <memory>
#include <type_traits>

using namespace std;

class A
{
public:
    ~A(){cout << "~A()" << endl;}
     A& operator=(const A&) { return *this; }
};

int main() 
{    
    cout << boolalpha << endl;
    
    cout << is_object<int>::value << endl;
    cout << is_object<int&>::value << endl;
    cout << is_object<A>::value << endl;
    cout << is_object<A&>::value << endl;
    
    cout << "-----------------------------" << endl;
    
    struct B {};
 
    enum class C {};   
    
    cout << is_class<A>::value << endl;
    cout << is_class<B>::value << endl;
    cout << is_class<C>::value << endl;
    cout << is_class<int>::value << endl;    
    
    cout << "-----------------------------" << endl;
    
    function<void()> f = [](){cout << "is function object move assignable ?" << endl;};

    cout << is_move_assignable<B>::value << endl;
    cout << is_move_assignable<C>::value << endl;
    cout << is_move_assignable<decltype(f)>::value << endl;
    cout << is_move_assignable<A>::value << endl;
 
}

Başka bir kullanım alanları ise şablon (template) fonksiyonları için bir nevi koşul veya kısıtlama getirmek için de kullanılırlar. Tipik olarak std::enable_if ile kullanılırlar, std::enable_if temel olarak SFINAE (Substitution failure is not an error) prensibine dayanır. Kısacası derleyici, şablon fonksiyon overloadlarını yazarken oluşan hatayı görmezden gelir ve diğer fonksiyonları yazmaya devam eder., Örneğin:

using namespace std;

class A
{   
public:
    
    friend ostream &operator<<(ostream &os, const A&) 
    {
        os << "class A" << endl; 
    }
};

template <class T>
typename enable_if<is_integral<T>::value, T>::type
println(T a)
{
    cout << "if T is a integral type: " << a << endl;
};


template <class T>
typename enable_if<is_object<T>::value, T>::type
println(T &a)
{
    cout << "if T is a object type: " << a << endl;
};

int main(int argc, char**argv) 
{
    
    A a;
    
    println(5);
    println(a);
    
    return 0;
}

Örnekte görüldüğü üzere println fonksiyonu iki farklı şekilde şablonu üretiliyor. “enable_if::value, T>::type” ifadesi ile sadece istenen türlerle o fonksiyonun çağrılmasına olanak veriyor. is_object aslında derleme zamanında gelen türün nesne mi olup olmadığını test etmemizi sağlıyor. Böylelikle kullanıma göre derleme zamanında uygun şablon fonksiyonlar yazılacak.

type traitslerin aşağıdaki linkte bir listesini bulabilirsiniz:

http://en.cppreference.com/w/cpp/types

C++11 – std::tuple üzerine

STL’ i kullananlar bilirler genelde iki farklı türden anlamsal birliktelikleri olan değişkenleri tutmak için genelde std::pair kullanılırdı.

pair<int, string> pt (1, "herbibk");
cout << pt.first << "-" << pt.second << endl;

C++11 ile gelen veri yapılarından biri olan std::tuple’ la birlikte bu özellik sınırsız türde nesneyi sınırsız sayıda içermeye kadar gitti. Üstelik derleme zamanında veri yapısındaki nesne sayısını da biliyorsunuz.

Aslında tuple’ ın nasıl implemente edildiğine baktığınızda basitçe C++’ ın iki temel özelliğini kullanılmış; Çoklu Türetme (Multiple Inheritance) ve Değişken parametreli şablonlar (variadic templates).

template <typename T>
struct tuple_leaf
{
T value_;
};

template <typename... Types>
class tuple : tuple_leaf<Types>...
{
};

‘tuple_leaf…’ parametre pack’i (türkçesini daha düşünmedim) ifadesi tuple_leaf, tuple_leaf, tuple_leaf….tuple_leaf şeklinde açılacak böylelikle tuple sınıfı da bunlardan çoklu türetmeyle türetilecek.

Fakat burda şöyle bir sıkıntı var, aynı türde nesneler geldiğinde örneğin T1 ve T3 string olduğunda bu yapı çalışmayacak zaten var olan bir sınıf için ikinci defa bir sınıf yazılmayacak ve derleme hatası verilecektir. STL de bu sıkıntıyı aşmak için std::integer_sequence kullnılmış derleme zamanında indis üretmek için. Böylelikle aynı tür nesneler geldiğin de bile indisleri farklı olduğundan farklı bir sınıftan türetiliyormuş gibi olacak. Biraz zorlama bir trick olmuş .)

Peki daha önceki variadic template fonksiyonlarından bahsettiğimiz yazılarımdaki1 gibi recursive bir tanım yaparsak bu sorunları aşabilir miyiz ?

template <class... Ts> struct tuple {};

template <class T, class... Ts>
struct tuple<T, Ts...> : tuple<Ts...> {
tuple(T t, Ts... ts) : tuple<Ts...>(ts...), tail(t) {}

T tail;
};

Yukarılda görüldüğü gibi rekürsiv olarak her bir tür için bir sınıf diğer kalan türlerden türetilerek oluşturulacak.

Aslında şöyle bir yapı ortaya çıkacak;

struct tuple<T1, T2, T3> : tuple<T2, T3> {
T1 tail;
}

struct tuple<T2, T3> : tuple<T3> {
T2 tail;
}

struct tuple<T3&>: tuple {
T3 tail;
}

struct tuple {
}

Rekürsiv olarak tanımlamak biraz kafa karıştırsa da en kısa yol. Standart kütüphaneye bakıldığında bu işi non-recursive yoldan halletmişler. Tuple implementasyonunu meta programming bakış açınızı güçlendirmek adına inceleyebilirsiniz.

Göz at:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4296.pdf
http://blogs.microsoft.co.il/sasha/2015/01/12/implementing-tuple-part-1/