← Kursa Dön
📄 Text · 12 min

auto, decltype ve Tür Çıkarımı

C++'ın ilk yıllarında her değişkenin tipini açıkça yazman gerekiyordu. std::vector<std::pair<std::string, int>>::const_iterator it = ... gibi satırlar hem okunması zor, hem de yazılması zahmetliydi. Modern C++ bu sorunu tür çıkarımı (type deduction) ile çözdü.

Bir garson analojisi düşün: "Aynısından bir tane daha" dediğinde garson senin ne içtiğini zaten biliyor — tekrar "soğuk demlenmiş filtre kahve, orta boy, az şekerli" demene gerek yok. auto da aynı şeyi yapar: derleyici sağ taraftaki ifadeye bakarak tipi kendisi çıkarır.

Bu derste auto, decltype ve decltype(auto) mekanizmalarını öğreneceğiz.


auto ile Değişken Tanımlama

auto anahtar kelimesi, derleyiciye "bu değişkenin tipini sen çıkar" der. Değişkenin tipi, başlatma ifadesinden (initializer) otomatik belirlenir.

#include <vector>
#include <map>
#include <string>
#include <iostream>

int main() {
    // Temel tipler
    auto x = 42;          // int
    auto y = 3.14;        // double
    auto z = 'A';         // char
    auto name = std::string("Ali");  // std::string
    auto flag = true;      // bool

    // STL container'ları — auto çok faydalı
    auto numbers = std::vector<int>{1, 2, 3, 4, 5};
    auto ages = std::map<std::string, int>{{"Ali", 25}, {"Ayse", 30}};

    // Iterator — auto olmasa çok uzun tip yazardın
    auto it = numbers.begin();  // std::vector<int>::iterator
    auto cit = ages.cbegin();   // std::map<std::string,int>::const_iterator

    std::cout << "x tipi int: " << x << "\n";
    std::cout << "Iterator: " << *it << "\n";

    return 0;
}

auto ile Referans ve const

auto varsayılan olarak değer kopyası oluşturur. Referans veya const istiyorsan açıkça belirtmelisin:

#include <vector>
#include <iostream>

int main() {
    std::vector<int> nums = {10, 20, 30};

    // Kopya — orijinal etkilenmez
    auto val = nums[0];
    val = 99;
    std::cout << "nums[0]: " << nums[0] << "\n";  // 10 — değişmedi

    // Referans — orijinali etkiler
    auto& ref = nums[0];
    ref = 99;
    std::cout << "nums[0]: " << nums[0] << "\n";  // 99 — değişti!

    // const referans — okuma için, kopyalama olmadan
    const auto& cref = nums[1];
    std::cout << "cref: " << cref << "\n";  // 20
    // cref = 50;  // HATA! const referans

    // Pointer
    auto ptr = &nums[2];
    std::cout << "*ptr: " << *ptr << "\n";  // 30

    return 0;
}

Range-Based For'da auto

#include <vector>
#include <string>
#include <iostream>

int main() {
    std::vector<std::string> names = {"Ali", "Ayse", "Mehmet"};

    // Kopya — her iterasyonda string kopyalanır (yavaş!)
    for (auto name : names) {
        std::cout << name << " ";
    }
    std::cout << "\n";

    // const referans — kopyalama yok, değiştirme yok (en yaygın)
    for (const auto& name : names) {
        std::cout << name << " ";
    }
    std::cout << "\n";

    // Referans — değiştirmek istersen
    for (auto& name : names) {
        name = "Degisti";
    }

    return 0;
}

💡 Kural: Range-based for'da const auto& kullanmayı alışkanlık edin. Değiştirmek istiyorsan auto&, kopyalamak istiyorsan (nadir) auto.


auto ile Fonksiyon Dönüş Tipi

C++14'ten itibaren fonksiyonların dönüş tipi olarak auto kullanabilirsin. Derleyici return ifadesinden tipi çıkarır.

#include <vector>
#include <string>
#include <iostream>

// Dönüş tipi auto — derleyici int çıkarır
auto add(int a, int b) {
    return a + b;
}

// Template ile birlikte
template<typename T>
auto square(T x) {
    return x * x;
}

// Birden fazla return — tipleri aynı olmalı
auto classify(int score) -> std::string {
    if (score >= 90) return "A";
    if (score >= 80) return "B";
    if (score >= 70) return "C";
    return "F";
}

int main() {
    std::cout << add(3, 5) << "\n";       // 8
    std::cout << square(4) << "\n";       // 16
    std::cout << square(3.5) << "\n";     // 12.25
    std::cout << classify(85) << "\n";    // B

    return 0;
}

Trailing Return Type (Sondaki Dönüş Tipi)

C++11'de tanıtılan bu söz dizimi, dönüş tipini fonksiyon parametrelerinden sonra yazar:

#include <iostream>

// Trailing return type — parametreleri görerek tip belirle
template<typename T, typename U>
auto multiply(T a, U b) -> decltype(a * b) {
    return a * b;
}

int main() {
    auto result1 = multiply(3, 4.5);    // double
    auto result2 = multiply(2, 3);      // int

    std::cout << result1 << "\n";  // 13.5
    std::cout << result2 << "\n";  // 6

    return 0;
}

decltype — İfadenin Tipini Alma

decltype bir ifadenin tipini derleyici zamanında belirler. auto başlatma ifadesinden tip çıkarır; decltype ise herhangi bir ifadenin tipini alır — o ifadeyi çalıştırmadan.

#include <vector>
#include <iostream>

int main() {
    int x = 42;
    double y = 3.14;

    // decltype — ifadenin tipini al
    decltype(x) a = 10;      // int (x'in tipi)
    decltype(y) b = 2.71;    // double (y'nin tipi)
    decltype(x + y) c = 0;   // double (int + double = double)

    // Container'ın eleman tipi
    std::vector<int> nums = {1, 2, 3};
    decltype(nums)::value_type val = 42;  // int

    // Fonksiyon dönüş tipinden yararlanma
    decltype(nums.size()) count = 0;  // std::vector<int>::size_type

    std::cout << "a: " << a << " (int)\n";
    std::cout << "c: " << c << " (double)\n";

    return 0;
}

decltype'ın İnce Noktaları

decltype iki farklı davranış sergiler:

  • İsim verilirse (parantez olmadan): o ismin tanımlandığı tip döner

  • İfade verilirse (parantezli dahil): ifadenin değer kategorisini de dikkate alır

#include <iostream>

int main() {
    int x = 42;

    decltype(x) a = 10;     // int — x bir isim
    decltype((x)) b = x;    // int& — (x) bir lvalue ifade!

    b = 99;
    std::cout << "x: " << x << "\n";  // 99 — b referans olduğu için!

    return 0;
}

⚠️ `decltype(x)` ile `decltype((x))` farklıdır! Tek parantez eklemek sonucu tamamen değiştirebilir. decltype(x) int dönerken, decltype((x)) int& döner çünkü (x) bir lvalue ifadedir.


auto vs Explicit Type — Ne Zaman Hangisi?

auto her yerde kullanılmalı mı? Hayır. Bazı durumlarda açık tip yazmak daha iyidir.

auto Kullan

// 1. Iterator tipleri — çok uzun
auto it = myMap.find("key");

// 2. Lambda — tipi zaten yazılamaz
auto lambda = [](int x) { return x * 2; };

// 3. Sağ taraf tipi açık belli
auto name = std::string("Ali");
auto count = static_cast<int>(vec.size());

// 4. Template fonksiyon dönüşleri
auto result = someTemplateFunction<int, double>(args);

Explicit Tip Yaz

// 1. Tip belirsizliği olabilecek durumlar
double ratio = 1 / 3;   // 0! (int / int)
// auto ratio = 1 / 3;  // 0 — niyetini belli etmiyor

double ratio2 = 1.0 / 3;  // 0.333... — açık ve net

// 2. API sınırlarında — fonksiyon imzaları
int calculateScore(const Student& s);  // Tip bilgisi dokümantasyon

// 3. Tip dönüşümü önemliyse
size_t index = 0;      // İşaretsiz olması önemli
int32_t errorCode = 0; // Belirli genişlik önemli

Genel Kılavuz

DurumTercih
Iterator tipleriauto
Lambda değişkenleriauto
Basit literal'ler (int x = 5)İkisi de olur
Açık tip dönüşümü gereken yerlerExplicit ✅
Fonksiyon parametreleriExplicit ✅ (auto sadece generic lambda'da)
Range-based forconst auto&

decltype(auto)

C++14'te gelen decltype(auto), auto'nun tür çıkarım kuralları yerine decltype'ın kurallarını kullanır. En sık kullanıldığı yer: referans döndüren fonksiyonlarda dönüş tipinin doğru korunması.

#include <iostream>
#include <string>
#include <vector>

std::vector<std::string> names = {"Ali", "Ayse", "Mehmet"};

// auto — referansı kaybeder, kopya döner
auto getFirst_auto() {
    return names[0];  // string kopyası döner
}

// decltype(auto) — referansı korur
decltype(auto) getFirst_decltype() {
    return names[0];  // string& döner (operator[] referans döndüğü için)
}

int main() {
    auto copy = getFirst_auto();         // string kopyası
    decltype(auto) ref = getFirst_decltype();  // string& referansı

    ref = "Degisti";
    std::cout << names[0] << "\n";  // Degisti — referans ile değiştirdik

    return 0;
}

Perfect Forwarding'de decltype(auto)

#include <iostream>
#include <utility>

template<typename F, typename... Args>
decltype(auto) call(F&& f, Args&&... args) {
    return std::forward<F>(f)(std::forward<Args>(args)...);
}

int& getRef(int& x) { return x; }
int getValue() { return 42; }

int main() {
    int x = 10;

    // Referans dönen fonksiyonu çağır — decltype(auto) referansı korur
    decltype(auto) ref = call(getRef, x);
    ref = 99;
    std::cout << x << "\n";  // 99

    // Değer dönen fonksiyonu çağır
    decltype(auto) val = call(getValue);
    std::cout << val << "\n";  // 42

    return 0;
}

💡 `decltype(auto)` ne zaman kullan? Sadece dönüş tipinin referans mı yoksa değer mi olduğunun korunması gerektiğinde. Günlük kullanımda auto çoğu zaman yeterlidir.


Structured Bindings ile auto (Ön Bakış)

C++17'de gelen structured bindings, auto ile birden fazla değeri aynı anda açar (decompose). Detayları bir sonraki derste göreceğiz ama burada kısaca gösterelim:

#include <map>
#include <string>
#include <iostream>

int main() {
    std::map<std::string, int> ages = {{"Ali", 25}, {"Ayse", 30}};

    // Structured binding — auto ile birden fazla değişken
    for (const auto& [name, age] : ages) {
        std::cout << name << ": " << age << "\n";
    }

    // Pair açma
    auto [minIt, maxIt] = std::minmax({3, 1, 4, 1, 5, 9});
    std::cout << "Min: " << minIt << ", Max: " << maxIt << "\n";

    return 0;
}

Pratik Örnekler

Tip Güvenli Hesaplama

#include <vector>
#include <numeric>
#include <iostream>

int main() {
    std::vector<int> scores = {85, 92, 78, 95, 88};

    // size_type ile güvenli karşılaştırma
    auto total = std::accumulate(scores.begin(), scores.end(), 0.0);
    auto count = scores.size();  // size_t (unsigned)
    auto average = total / count;

    std::cout << "Ortalama: " << average << "\n";

    // decltype ile uyumlu tip oluşturma
    decltype(average) bonus = 5.0;
    std::cout << "Bonuslu: " << average + bonus << "\n";

    return 0;
}

auto ile Template Basitleştirme

#include <map>
#include <vector>
#include <string>
#include <iostream>

int main() {
    // auto olmadan — tür cehenneminden bir kesit
    std::map<std::string, std::vector<int>> studentGrades;
    studentGrades["Ali"] = {85, 90, 78};
    studentGrades["Ayse"] = {95, 88, 92};

    // auto ile — çok daha temiz
    for (const auto& [name, grades] : studentGrades) {
        auto sum = 0.0;
        for (const auto& g : grades) {
            sum += g;
        }
        auto avg = sum / grades.size();
        std::cout << name << ": " << avg << "\n";
    }

    return 0;
}

Özet

  • `auto` derleyicinin tipi başlatma ifadesinden çıkarmasını sağlar. Iterator tipleri, lambda değişkenleri ve karmaşık template tipleri için çok faydalıdır.

  • `auto&` referans, `const auto&` const referans oluşturur. Range-based for'da const auto& varsayılan tercihin olmalı.

  • `auto` fonksiyon dönüş tipi olarak (C++14+) kullanılabilir. Trailing return type (-> tip) C++11'de parametrelere bağlı dönüş tipi belirtmek için kullanılır.

  • `decltype` bir ifadenin tipini derleyici zamanında çıkarır — değişken tanımlamada, template'lerde ve tip kontrolünde kullanışlıdır. decltype(x) ve decltype((x)) farklı sonuç verebilir.

  • `decltype(auto)` auto yerine decltype kuralları ile tür çıkarımı yapar — referans döndüren fonksiyonlarda dönüş tipini korumak için kullanılır.

  • Genel kılavuz: Tip açıksa auto kullan, niyetin belli olması gerekiyorsa veya tip dönüşümü önemliyse explicit tip yaz.