C++ Dilinde constraints ve concepts
- Yusuf Hançar
- Mar 1
- 31 min read
C++ dilinde template'ler, esneklik sağlarken aynı zamanda bazı problemlere de yol açabilir. Her template her tür için uygun olmayabilir ve yanlış bir template argümanı kullanıldığında ortaya çıkan derleyici hataları genellikle karmaşık ve anlaşılması zor olabilir. Ayrıca, derleyicinin gereksiz iş yüküyle karşı karşıya kalmaması için belirli türler üzerinde kısıtlamalar getirmenin faydalı olacağı uzun süredir bilinen bir gereksinimdi.
C++20 ile birlikte bu problemi çözmek için concept kavramı standart hale getirildi. Concepts, template argümanları üzerinde belirli kısıtlamalar getirerek yalnızca uygun türlerin kullanılmasını sağlar.
Bu sayede :
Hatalı template argümanı kullanıldığında derleyici daha açıklayıcı hata mesajları verebilir.
SFINAE (Substitution Failure Is Not An Error) gibi eski ve karmaşık teknikler yerine daha anlaşılır bir yapı sunar.
Derleyicinin yükünü azaltarak daha verimli derleme süreçleri sağlar.
User defined constraints oluşturulabileceği gibi, STL tarafından sağlanan hazır concept'ler de kullanılabilir.
Bu yazımızda, concept kavramının nasıl çalıştığını, template kısıtlamalarını nasıl daha okunaklı hale getirdiğini ve derleyici performansına nasıl katkı sağladığını inceleyeceğiz...
Concept | Constraint |
integral signed_integral unsigned_integral floating_point | Integral type Signed integral type Unsigned integral type Floating-point type |
movable copyable semiregular regular | Supports move initialization/assignment and swaps Supports move and copy initialization/assignment and swaps Supports default initialization, copies, moves, and swaps Supports default initialization, copies, moves, and swaps and equality comparisons |
same_as convertible_to derived_from constructible_from assignable_from swappable_with common_with common_reference_with | Same types Type convertible to another type Type derived from another type Type constructible from others types Type assignable from another type Type swappable with another type Two types have a common type Two types have a common reference type |
equality_comparable equality_comparable_with totally_ordered totally_ordered_with three_way_comparable three_way_comparable_with | Type support checks for equality Can check two types for equality Types support a strict weak ordering Can check two types for strict weak ordering Can apply all comparison operators(including the operator<=> Can compare two types with all comparison operators(including <=> |
invocable regular_invocable predicate relation equivalence_relation strict_weak_order uniform_random_bit_generator | Type is a callable for specified arguments Type is a callable for specified arguments(no modifications) Type is a predicate (callable that returns a Boolean value) A callable type defines a relationship betweem two types A callable type defines an equality relationship between two types A callable type defines an ordering relationship between two types A callable type can be used as a random number generator |
#include <iostream>
#include <concepts>
using namespace std;
template <std::integral T>
void func(T x)
{
cout << "integer types" << "\n";
}
template <std::floating_point T>
void func(T x)
{
cout << "float types" << "\n";
}
int main ()
{
func(10);
func(10.5F);
}
*****************************
AÇIKLAMA : c++20
*****************************
CEVAP :
integer types
float types
*****************************
abbreviated types ile de yazılabilir
void func(std::integral auto)
{
cout << "integer types" << "\n";
}
void func(std::floating_point auto)
{
cout << "float types" << "\n";
}
int main ()
{
func(10);
func(10.5F);
}
*****************************
AÇIKLAMA : c++20
func yine template
*****************************
CEVAP :
integer types
float types
*****************************
concept kendisi de bir template kategorisi ve bir keyword Ayrıca isimlendirilmiş ya da isimlendirilmemiş constraints olabilir.
sadece tam sayı türleri ile kısıtlanmış bir fonksiyon şablonu
template <typename T, std::enable_if_t<std::is_integral_v<T>>* = nullptr>
void foo(T);
int main ()
{
foo(10.5);
}
*****************************
AÇIKLAMA : template parametresinde kullanılabilir.
enable_if 2.parametresi default void ve geri dönüş değerini ifade eder.
*****************************
CEVAP : synyax error : no matching function for call to ‘foo(double)’
*****************************
template <typename T>
std::enable_if_t<std::is_integral_v<T>, T> foo(T);
int main ()
{
foo(10.5);
}
*****************************
AÇIKLAMA : fonksiyonun geri dönüş değerinde kullanılabilir.
void yerine T yazdık ve geri dönüş değeri T oldu.
*****************************
CEVAP : synyax error
*****************************
template <typename T>
void foo(T, std::enable_if_t<std::is_integral_v<T>>* p = nullptr);
int main ()
{
foo(10.5);
}
*****************************
AÇIKLAMA : fonksiyonun parametresine default argument yapabiliriz.
*****************************
CEVAP : synyax error
*****************************
Bir template e ait constraint olan ve uyulmadığı senaryo...
template <typename T>
void foo(const T& v)
{
std::cout << v << "/";
}
int main ()
{
foo(2025);
foo(22.02);
foo(std::string{ "smartcode" });
}
*****************************
AÇIKLAMA : legal
*****************************
CEVAP : 2025/22.02/smartcode/
*****************************
template <typename T>
void foo(const T& v)
{
std::cout << v;
}
int main ()
{
std::vector vec{ 10, 5, 8, 1 };
foo(vec);
}
*****************************
AÇIKLAMA : bu template constraint edilmiş olsaydı hem interface de yer alır hem de bakınca daha anlaşılır olurdu.
std::vector<int> türü doğrudan std::cout ile yazdırılamaz!
Bu durumda derleyici, std::cout << v; ifadesini işlerken, std::vector<int> türü için uygun bir operator<< olmadığını fark eder ve karmaşık bir hata mesajı döndürür.
*****************************
CEVAP : syntax error
*****************************
Bir template i constraint etmenin yöntemleri :
requires clause
prefix
trailing
requires expression
named constraints = > concepts
requires clause
prefix
parametre değişkeninin ismi visible değildir
template <typename T>
requires (sizeof(T) > 2)
void foo(T v)
{
std::cout << "foo called with T = " << typeid(T).name() << "\n";
}
int main ()
{
foo('a');
}
*****************************
AÇIKLAMA :
sizeof(T) > 2) bu kısım requires
compile time da boolean değerlendirilecek bir gereklilik .
*****************************
CEVAP : requirement constraint edilmediği için syntax error.
-> foo(10) genellikle 4 byte oldugundan gecerlidir.
*****************************
template <typename T>
requires std::is_integral_v<T> && (sizeof(T) > 2)
void foo(T)
{
}
int main ()
{
foo(6); // T = int
}
*****************************
AÇIKLAMA : Şartlar sağlandığı için derleyici foo(6); çağrısını kabul eder ve derleme başarılı olur.
*****************************
CEVAP :
*****************************
! kullanıldığında parantez içine alma şartı vardır.
template <typename T>
requires (!std::is_integral_v<T>)
void foo(T)
{}
int main ()
{
foo(6);
}
*****************************
AÇIKLAMA : tam sayı türü olmamalıdır.
*****************************
CEVAP : syntax error
*****************************
template <typename T>
requires true
void foo(T)
{}
int main ()
{
foo(6);
}
*****************************
AÇIKLAMA : requires true her zaman sağlandığı için hiçbir kısıtlama uygulanmamış olur.
*****************************
CEVAP : legal
*****************************
trailing
parametre değişkeninin ismi visible ve kullanılabilir.
Özellikle aşırı yükleme (overloading) ve daha uzun bildirimler için okunabilirliği artırabilir
template <typename T>
void foo(T x)requires (sizeof(T) > 2);
int main ()
{
foo('a');
}
*****************************
AÇIKLAMA :
sizeof(T) > 2) bu kısım requires
compile time da boolean değerlendirilecek bir gereklilik .
*****************************
CEVAP :
requirement constraint edilmediği için syntax error.
*****************************
requires expression
Bir ifadenin geçerli olup olmadığını derleme zamanında kontrol etmek için kullanılır.
template <typename T>
requires (sizeof(T) > 2) && // requires clause
requires {typename T::size_type;} && // requires expression (size_type diye bir nested type olmalı
std::input_iterator<T> // concept
template <typename T>
requires std::is_pointer_v<T> void foo(T);
*****************************
AÇIKLAMA : pointer türleri için true olan compile time constraint
*****************************
CEVAP : legal
*****************************
template <typename T>
requires std::is_pointer_v<T> || std::is_reference_v<T>
class Data;
int main ()
{
Data<int>
}
*****************************
AÇIKLAMA : pointer ya da reference türü değildir.
*****************************
CEVAP : syntax error
*****************************
concepts
constraintleri isimlendirilmiş hale getiririz ve her defasında bu constraintleri requires clause ya da requires expression ile yazmak yerine concept doğrudan kullanılır.
template <typename T>
concept Integral = std::is_integral_v<T>;
template <typename T>
requires Integral<T> void foo(T);
int main ()
{
foo(11);
}
*****************************
AÇIKLAMA : Integral concept tanımlayarak prefix requires clause ile kullanıldı.
*****************************
CEVAP : legal
*****************************
isimlendirmenin bir diğer faydası template kodları yazarken c++20 öncesi mümkün olmayan bazı olanakları sağlar.
typename anahtar sözcüğü yerine isimlendirilen concept kullanılabilir.
template <typename T>
concept Integral = std::is_integral_v<T>;
// constrained template parameter
template <Integral T>
void func(T);
int main ()
{
func(12); // legal
func<int>(10); // legal
func(5.4); // syntax error
func<double>(5.4); // syntax error
}
template <typename T>
concept Integral = std::is_integral_v<T>;
template <Integral T, Integral U>
class Data {};
int main ()
{
Data<int, long> val; // legal
Data<double, long> val; // syntax error
}
non-type parametre de constraint edilebilir.
template <std::size_t n>
requires (n > 10)
class Data {};
int main ()
{
Data<11> d;
}
*****************************
AÇIKLAMA : 11 > 10
*****************************
CEVAP : legal
*****************************
#include <iostream>
#include <concepts>
#include <type_traits>
template <typename T>
concept Integral = std::is_integral_v<T>;
template <Integral T>
concept SignedIntegral = Integral<T> && std::is_signed_v<T>;
template <Integral T>
concept UnSignedIntegral = Integral<T> && !SignedIntegral<T>;
// T1 ve T2'nin ikisinin de Integral olması gerekiyor.
template <Integral T1, Integral T2>
struct Data
{
T1 first;
T2 second;
};
int main()
{
Data<int, long> val1; // ✅ Legal, çünkü int ve long tamsayı türleri.
// Data<double, long> val2; // ❌ Derleme hatası, çünkü double tamsayı değil.
}
dönüşüm kontrolü içinde constraint oluşturulabilir.
std::is_convertible_v
template <typename T>
concept as_int = std::integral<T> || std::is_convertible_v<T, int>;
// T ya integral tür olmalı ya da int'e dönüşebilir olmalı.
template <typename T>
requires as_int<T>
void foo()
{
std::cout << "foo called with type: " << typeid(T).name() << '\n';
}
struct Data {};
int main()
{
foo<int>(); // ✅ Legal (int bir integral türdür)
foo<double>(); // ✅ Legal (double, int'e dönüşebilir)
// foo<std::string>(); // ❌ Derleme hatası! std::string, int'e dönüşemez.
foo<Data>(); // ❌ syntax error
}
template <typename T>
concept as_int = std::integral<T> || std::is_convertible_v<T, int>;
// T ya integral tür olmalı ya da int'e dönüşebilir olmalı.
template <typename T>
requires as_int<T>
void foo()
{
std::cout << "foo called with type: " << typeid(T).name() << '\n';
}
struct Data {
operator int ()const;
};
int main()
{
foo<Data>(); // ✅legal
}
template <typename T>
concept as_int = std::integral<T> || std::is_convertible_v<T, int>;
template <as_int T>
class Data {};
struct Nest {};
int main ()
{
Data<Nest> d;
}
*****************************
AÇIKLAMA :
*****************************
CEVAP : syntax error
*****************************
struct Nest
{
operator int() const { return 11; } // `Nest` → `int` dönüşümü sağlandı.
};
int main()
{
Data<Nest> d; // ✅ Artık çalışır.
}
fonksiyon şablonlarında ya da auto kullanılan her yerde burdaki isimlendirilen constraint auto ile birlikte kullanılabilir.
abbreviated type en güzel örneğidir.
template <typename T>
concept as_int = std::integral<T> || std::is_convertible_v<T, int>;
template <as_int T>
void foo(T x)
{
std::cout << "foo called with: " << x << '\n';
}
int main()
{
foo(10); // ✅ Legal, çünkü int bir integral türdür.
foo(3.5); // ✅ Legal, çünkü double → int dönüşümü mümkündür.
// foo("smartcoding"); // ❌ Derleme hatası, çünkü std::string int'e dönüşemez.
}
template <typename T>
concept as_int = std::integral<T> || std::is_convertible_v<T, int>;
void foo(as_int auto x){}
int main ()
{
foo(11);
}
*****************************
AÇIKLAMA : yukarıdaki yerine bu şekilde yazılabilir.
*****************************
standart concept ile örnek
void foo(std::convertible_to<std::string> auto x)
{
std::cout << "param is : " << x << "\n";
}
int main ()
{
foo("smartcoding");
foo(10); // ❌ syntax error
}
*****************************
AÇIKLAMA :
*****************************
CEVAP : param is : smartcoding
*****************************
member template olabilir.
class Data {
public :
void foo(std::integral auto v);
};
int main ()
{}
*****************************
AÇIKLAMA : sınıfın üye fonksiyonunu constraint ettik.
*****************************
auto kullanılan her yerde concept ile beraber auto kullanılabilir.
int main ()
{
auto val = 10;
}
*****************************
AÇIKLAMA : auto type deduction
*****************************
int foo();
int main ()
{
auto val = foo();
}
*****************************
AÇIKLAMA : auto type deduction
*****************************
int foo();
int main ()
{
std::integral auto val = foo();
}
*****************************
CEVAP : legal
*****************************
double foo();
int main ()
{
std::integral auto val = foo();
}
*****************************
CEVAP : syntax error
*****************************
auto foo(int x)
{
return x;
}
*****************************
AÇIKLAMA : auto return type
*****************************
auto foo(int x)
{
return x;
}
*****************************
AÇIKLAMA : auto return type
*****************************
std::integral auto foo(int x)
{
return x * 5.4;
}
*****************************
AÇIKLAMA : auto return type
*****************************
CEVAP : syntax error
*****************************
int main ()
{
std::vector<int> ivec(10);
for (std::integral auto x : ivec)
{
std::cout << x;
}
}
*****************************
CEVAP : 0000000000
*****************************
sabit ifadesi kullanılan yerlerde kullanılabilir.
if constexpr
template <typename T>
void foo(T x)
{
if constexpr (std::integral<T>)
{
cout << "tam sayi turleri icin" << "\n";
}
else if constexpr (std::floating_point<T>)
{
cout << "floating turleri icin" << "\n";
}
else
{
cout << "diger turler icin" << "\n";
}
}
int main ()
{
foo('a');
foo(10.4);
foo("smartcoding");
}
*****************************
CEVAP :
tam sayi turleri icin
floating turleri icin
diger turler icin
*****************************
template <typename T>
concept additive = requires (T x, T y) {
x + x;
x - y;
};
template <typename T>
void foo(T x)
{
if constexpr (additive<T>)
{
cout << "kosul saglanan durum" << "\n";
}
else
{
cout << "kosul saglanmayan durum" << "\n";
}
}
int main ()
{
foo(10);
}
*****************************
AÇIKLAMA : toplama ve çıkarma işlemlerini sağlıyor.
*****************************
CEVAP : kosul saglanan durum
*****************************
template <typename T>
concept additive = requires (T x, T y) {
x + x;
x - y;
};
template <typename T>
void foo(T x)
{
if constexpr (additive<T>)
{
cout << "kosul saglanan durum" << "\n";
}
else
{
cout << "kosul saglanmayan durum" << "\n";
}
}
int main ()
{
int sc{10};
int* ptr = ≻
foo(ptr);
}
*****************************
AÇIKLAMA : pointer çıkarma yapılır ancak toplama sağlanmadığı için
*****************************
CEVAP : kosul saglanmayan durum
*****************************
std::bit
std::has_single_bit
constexpr noexcept ve bool dönen fonksiyon şablonudur.
işaretsiz tam sayı türleri için sadece bir biti 1 mi onu kontrol eder.
constexpr olduğuna göre concept ile de kullanabiliriz.
template <std::size_t N>
requires (std::has_single_bit(N)) && (N > 32)
class Data {};
int main ()
{
Data<5> d1; // syntax error
Data<64> d2; // legal
}
*****************************
AÇIKLAMA : N sabitinin sadece tek biti 1 olmalıdır.
*****************************
constexpr bool isprime(int x)
{
if (x < 2)
{
return false;
}
if (x % 2 == 0)
{
return x == 2;
}
if (x % 3 == 0)
{
return x == 3;
}
if (x % 5 == 0)
{
return x == 5;
}
for (int i{7}; i * i <= x; i += 2)
{
if (x % i == 0)
{
return false;
}
}
return true;
}
template <int N>
requires (isprime(N))
class Data {};
int main ()
{
Data<17> d; // legal
Data<18> d1; // syntax error
}
template <int N>
concept Prime = isprime(N);
template <int N>
requires Prime<N>;
void func();
int main ()
{
func<19>(); // legal
func<20>(); // error
}
constexpr bool isprime(int x)
{
if (x < 2)
{
return false;
}
if (x % 2 == 0)
{
return x == 2;
}
if (x % 3 == 0)
{
return x == 3;
}
if (x % 5 == 0)
{
return x == 5;
}
for (int i{7}; i * i <= x; i += 2)
{
if (x % i == 0)
{
return false;
}
}
return true;
}
template <int N>
concept Prime = isprime(N);
template <int N>
requires Prime<N>
class Data{};
int main ()
{
Data<17> d; // legal
Data<18> d1; // syntax error
}
requires expression
birden fazla constraint i uygun bir sentaks ile tanımlayabilmek için bir araçtır.
simple requirements
template <typename T>
concept Data = requires (T x)
{
// simple requirements
// yazılan ifadeler sonucu syntax error oluşmayacak demektir.
x++; // burada amaç x i artırmak değildir. x++ ifadesi geçerli ifade olmalıdır.
};
int main ()
{
static_assert(Data<int>); // ✅ legal (int artırılabilir)
static_assert(Data<int*>); // ✅ legal (pointer artırılabilir)
static_assert(Data<std::vector<int>::iterator>); // ✅ legal (iterator artırılabilir)
}
template <typename T>
concept Data = requires (T x)
{
x++;
};
struct Prop {};
int main ()
{
static_assert(Data<Prop>);
}
*****************************
AÇIKLAMA : Prop` yapısının `x++` ile artırılabilir olması bekleniyor
*****************************
CEVAP : syntax error
*****************************
template <typename T>
concept Data = requires (T x)
{
x++;
};
struct Prop {
Prop operator++(int);
};
int main ()
{
static_assert(Data<Prop>);
}
*****************************
CEVAP : legal
*****************************
template <typename T>
concept Data = requires (T x)
{
*x;
x[0];
};
int main ()
{
static_assert(Data<int>);
}
*****************************
AÇIKLAMA : int türündne bir nesne * operatorunun operandı olamaz.
*****************************
CEVAP : syntax error
*****************************
template <typename T>
concept Data = requires (T x)
{
*x;
x[0];
};
int main ()
{
static_assert(Data<int*>);
}
*****************************
AÇIKLAMA :
*x → int* için geçerlidir (dereference işlemi yapılabilir).
x[0] → int* için geçerlidir (pointer arithmetic ile kullanılabilir).
*****************************
CEVAP : legal
*****************************
template <typename T>
concept Data = requires (T p)
{
p == nullptr;
};
int main ()
{
constexpr auto c1 = Data<int*>; // true
constexpr auto c2 = Data<int>; // false
constexpr auto c3 = Data<std::unique_ptr<int>>; // true
}
*****************************
AÇIKLAMA : p nullptr ile karşılaştırılması legal olmalıdır.
*****************************
template <typename T>
concept Data = requires (T x)
{
x < x;
};
int main ()
{
constexpr auto val = Data<int>; // true
}
*****************************
AÇIKLAMA : T türü için `<` operatörü geçerli olmalı.
*****************************
CEVAP : int türü < operatörünü destekler (a < b geçerlidir).
*****************************
template <typename T>
concept Data = requires (T x)
{
x < x;
};
struct A {};
struct B {
bool operator<(B) const;
};
int main ()
{
constexpr auto val = Data<A>; // false
constexpr auto val1 = Data<B>; // true
}
template <typename T>
concept Data = requires (T x, T y)
{
x < y;
};
struct A {};
struct B {
bool operator<(B) const;
};
int main ()
{
constexpr auto val = Data<A>; // false
constexpr auto val1 = Data<B>; // true
}
*****************************
AÇIKLAMA : parametre sayısı ile constraint etkilenmez.
*****************************
template <typename T>
concept Data = requires (T x)
{
std::is_integral_v<T>; // Bu, bir ifade değil, bir sabit ifadedir.
};
int main ()
{
constexpr auto val = Data<double>; // true
}
*****************************
AÇIKLAMA : T türü tam sayı türünden olmak zorunda değildir.
*****************************
template <typename T>
concept Data = requires (T x)
{
*x;
**x;
};
int main ()
{
constexpr auto c1 = Data<int*>; // false
constexpr auto c2 = Data<int*>; // true
}
template <typename T>
concept Data = requires (T x)
{
++**x;
};
int main ()
{
constexpr auto val = Data<int **>; // true
constexpr auto str = Data<string **>; // false
}
*****************************
AÇIKLAMA : T türü iki kez dereference edilebilir (**x).
Dereference edilmiş değerin (**x) prefix increment (++) operatörüyle artırılabilmesi gerekir.
*****************************
template <typename T>
concept Printable = requires (T x)
{
std::cout << x;
};
int main ()
{
static_assert(Printable<int>); // ✅ int türü için `operator<<` tanımlıdır.
static_assert(Printable<double>); // ✅ double türü için de geçerlidir.
static_assert(Printable<std::string>); // ✅ std::string türü de yazdırılabilir.
// ❌ Derleme hatası! std::vector için `operator<<` tanımlı değil.
static_assert(Printable<std::vector<int>>);
}
*****************************
AÇIKLAMA : ✅ T türü için `operator<<` geçerli olmalı.
*****************************
template <typename T>
concept Printable = requires (T x)
{
x.print(); // T türünün çağırılabilir bir print fonksiyonu olmalıdır.
};
struct A {};
struct B {
void print();
};
int main ()
{
constexpr auto val = Printable<int>; // false
constexpr auto val1 = Printable<std::string>; // false
constexpr auto val2 = Printable<A>; // false
constexpr auto val3 = Printable<B>; // true
}
concept tanlamasında requires expression kullanılmak zorunda değildir.
template <typename T>
requires requires (T x) {
x + x; // ✅ Toplama operatörü tanımlı olmalı
x - x; // ✅ Çıkarma operatörü tanımlı olmalı
}
class Data {};
int main ()
{
Data<int> d1; // ✅ Geçerli, int için `+` ve `-` operatörleri tanımlıdır.
Data<double> d2; // ✅ Geçerli, double için `+` ve `-` operatörleri tanımlıdır.
// ✅ Geçerli, çünkü `std::string` için `+` tanımlıdır, `-` tanımlı değil.
Data<std::string> d3;
// ❌ Derleme hatası! `std::vector<int>` için `+` ve `-` tanımlı değil.
Data<std::vector<int>> d4;
}
*****************************
AÇIKLAMA : ilk requires ifadesi requires clause ve öğe olarak constexpr compile time sabiti alabilir.
*****************************
template <typename T>
requires requires (T x) {
x + x;
x - x;
} && std::integral<T>
class Data;
int main ()
{
}
*****************************
AÇIKLAMA : ilk requires ifadesi requires clause ve öğe olarak constexpr compile time sabiti alabilir.
// requires clause
requires requires (T x) {
x + x;
x - x;
} && std::integral<T>
// requires expression
requires (T x) {
x + x;
x - x;
}
// concept
std::integral<T>
*****************************
template <typename T>
concept Data = requires {
sizeof(int) > 100;
std::is_integral_v<T>;
};
int main ()
{
constexpr bool val = Data<float>; // true
}
*****************************
AÇIKLAMA : float int türü değil ve sizeof değeri 100 den büyük değil ama true,
çünkü bunlar koşul değildir.
*****************************
template <typename T>
concept Data = requires {
requires (sizeof(int) > 100);
requires (!std::is_integral_v<T>);
};
int main ()
{
constexpr bool val = Data<float>; // false
}
*****************************
AÇIKLAMA : nested requires clause durumu
artık bunlar koşul olarak ele alınır.
*****************************
template <typename T>
concept Data = requires {
requires (sizeof(T) > 100);
requires (!std::is_integral_v<T>);
};
struct A {
char buf[2000];
};
int main ()
{
constexpr bool val = Data<A>; // true
}
type requirements
derleyici bir türün var olduğunu ve türün kullanımının geçerli olduğunu sınamak zorundadır.
template <typename T>
concept Data = requires {
typename T::value_type;
};
int main ()
{
constexpr auto c1 = Data<int>; // false çünkü int türünün type'ı olamaz.
constexpr auto c2 = Data<vector<int>>; // true
constexpr auto c3 = Data<vector<int>::iterator>; // true
}
*****************************
AÇIKLAMA : böyle bir nested type olmak zorunda denir.
*****************************
template <typename T>
concept Data = requires {
typename T::value_type;
};
struct A {};
int main ()
{
constexpr auto val = Data<A>; // false
}
*****************************
AÇIKLAMA : ❌ value_type yok, Data<A> -> false
*****************************
template <typename T>
concept Data = requires {
typename T::value_type;
};
struct A {
using value_type = A;
};
int main ()
{
constexpr auto val = Data<A>; // true
}
nested type şeklinde olmak zorunda değildir.
template <typename T>
concept Data = true; // ✅ Her tür için true
template <typename T>
concept A = requires {
requires Data<T>; // ✅ Data<T> legal mi?
};
int main ()
{
constexpr auto val = A<int>; // true
}
template <typename T>
concept Data = std::is_move_constructible_v<T>;
int main ()
{
constexpr auto val = Data<int>;
std::cout << std::boolalpha;
std::cout << "Data<int>: " << val << "\n";
}
*****************************
AÇIKLAMA :
*****************************
CEVAP : true
*****************************
template <typename T>
requires (sizeof(T) > 64)
class Data {};
int main()
{
Data<std::mt19937> eng; // legal
}
*****************************
AÇIKLAMA : std::mt19937, C++'ın Mersenne Twister rastgele sayı üreticisidir.
*****************************
requires clause kullanımında && ya da || operatorleri ile constraintleri detaylandırabiliyoruz.
Özellikle || kullanımı compiler'ı oldukça yorar.
template <typename T>
requires std::is_pointer_v<T> || std::same_as<T, std::nullptr_t>
void foo(T){
std::cout << "Function foo called\n";
}
int main()
{
int ival{};
foo(&ival); // &ival, int* türündedir.
}
*****************************
AÇIKLAMA : std::same_as concept
std::is_same_v ise type traits.
*****************************
CEVAP : legal çünkü pointer türüdür.
*****************************
template <typename T>
requires std::is_pointer_v<T> || std::same_as<T, std::nullptr_t>
void foo(T) {
std::cout << "Function foo called\n";
}
int main()
{
foo(nullptr);
}
*****************************
AÇIKLAMA : nullptr türü std::nullptr_t olarak değerlendirilir.
*****************************
CEVAP : Function foo called
*****************************
std::convertible_to
<from, to> açılımı gibi 2 parametrelidir.
template <typename T>
requires (!std::convertible_to<T, std::string>)
void foo(T v) {
std::cout << v;
}
int main()
{
foo("smartcode"); // syntax error
foo(45); // legal
}
*****************************
AÇIKLAMA : şimdi T türünden string türüne dönüşüm olmaması gerekir.
*****************************
template <typename T, typename U>
requires std::convertible_to<T, U>
void foo(T, U){
std::cout << "Function foo called\n";
}
int main()
{
foo(7, 4.5); // legal
}
template <typename T, typename U>
requires std::convertible_to<T, U>
void foo(T, U){
std::cout << "Function foo called\n";
}
int main()
{
int x{};
foo(x, &x);
}
*****************************
AÇIKLAMA : int türünden int* türüne dönüşüm yok
*****************************
void foo(const std::convertible_to<std::string> auto& val)
{
std::string str = val;
std::cout << "foo called and : " << str <<"\n";
}
void bar(const auto& val)
requires std::convertible_to<decltype(val), std::string>
{
std::string str = val;
std::cout << "bar called and : " << str <<"\n";
}
// T türü std::string'e dönüştürülebilir olmalıdır
template <std::convertible_to<std::string> T>
void baz (const T& val)
{
std::string str = val;
std::cout << "baz called and : " << str <<"\n";
}
// requires clause ile yazıldı.
template <typename T>
requires std::convertible_to<T, std::string>
void func(const T& val)
{
std::string str = val;
std::cout << "func called and : " << str <<"\n";
}
int main()
{
std::string str = "smartcode";
foo(str);
bar(str);
baz(str);
func(str);
}
*****************************
AÇIKLAMA : abbrevaited sentaks ile tek parametre yazılır ve to olan görünmeyen template parametresi std::string e dönüştürülebilmeli anlamına gelir.
*****************************
CEVAP :
foo called and : smartcode
bar called and : smartcode
baz called and : smartcode
func called and : smartcode
*****************************
// Implicit dönüşümleri destekleyen fonksiyon
template <std::convertible_to<bool> T>
void foo(T val)
{
cout << static_cast<bool>(val) << "\n";
}
// Explicit dönüşümleri de destekleyen fonksiyon
template <typename T>
requires requires (T val) { static_cast<bool>(val); }
void foo_explicit(T val)
{
cout << static_cast<bool>(val) << "\n";
}
int main()
{
boolalpha(cout);
int val{ 11 };
foo(val); // ✅
foo(&val); // ✅
// foo(nullptr); // ❌
foo("smartcode"); // ✅
// foo(std::string{"ali"}); // ❌
// foo(std::make_unique<int>(125)); // ❌
cout << "---- Explicit Conversion ----\n";
foo_explicit(val); // ✅
foo_explicit(&val); // ✅
foo_explicit(nullptr); // ✅
foo_explicit("smartcode"); // ✅
foo_explicit(std::make_unique<int>(5)); // ✅
}
*****************************
AÇIKLAMA : T türü bool türüne dönüştürülebilmelidir.
*****************************
declval ile kullanımı...
std::declval<T>(), T türünden bir nesne oluşturmadan onun üyesine veya fonksiyonlarına erişmek için kullanılan bir yardımcı fonksiyondur.Özellikle SFINAE, konseptler, type-traits (std::is_detected, decltype) gibi meta-programlama tekniklerinde sıkça kullanılır.
std::declval<T>() gerçek bir nesne oluşturmaz ve çağrılamaz.Sadece ifade türü (decltype) çıkarımı için kullanılır.
template <class T>
typename std::add_rvalue_reference<T>::type declval() noexcept;
class Data {
public :
Data() = default;
int foo() const;
};
int main()
{
decltype(Data{}.foo()) dv;
std::cout << typeid(dv).name() << '\n';
}
*****************************
CEVAP : int
*****************************
class Data {
public :
Data(int);
int foo() const;
};
int main()
{
decltype(Data{}.foo()) dv;
std::cout << typeid(dv).name() << '\n';
}
*****************************
AÇIKLAMA : def constructor olmadığı için
*****************************
CEVAP : syntax error
*****************************
class Data {
public :
Data(int);
int foo() const;
};
int main()
{
decltype(declval<Data>().foo()) dv;
std::cout << typeid(dv).name() << '\n';
}
*****************************
AÇIKLAMA : def constructor yok ancak legal
unevaluated contexte geçici nesne oluşturma ifadelerinde ya da bir sınıf türünü kullanma gereken contekste geçici nesne ifadesi oluşturmak yerine declval geri dönüş değeri kullanılır.
*****************************
CEVAP : int
*****************************
template <typename T>
requires std::integral<std::remove_reference_t<decltype(*std::declval<T>())>>
void foo(T){
std::cout << "called foo\n";
}
int main()
{
int x{};
double d{};
std::optional op{45};
foo(&x); // legal (pointer to int) ( T türü int* oldu içerik operatoru ile oluşturulan ifadenin türü int oldu ve integral concept karşılandı.
foo(&d); // syntax error
foo(op); // legal (optional tür de dereference edilebilir
}
*****************************
AÇIKLAMA :
✅ T türü dereference edilebilir olmalı (yani *T geçerli olmalı).
✅ *T işleminin sonucu tam sayı türünde (integral) olmalı.
std::integral concept ve specialization'ı boolean değer olarak kullanılır.
*std::declval<T>() ifadesinin amacı T türünden bir nesne ifadesi oluşturmaktır.
Neden declval(*T()) ya da declval(*T{}) yazarak T türünden bir nesne oluşturmuyoruz ?
// declval bir function template
nedeni T() ve T{} kullanımı için default constructor olmalıdır.
Ancak her türün def constructor'ı olmak zorunda değildir.
declval bir fonksiyon ve T açılımı T&& döndürür.
*std::declval<T>() ifadesi T türünden bir nesne içerik operatorunun operandı olabilir demek ve decltpe ile bunun türü alınır.
yani T türünden bir nesne içerik operatoru ile dereference edildiğinde o ifadenin türünü kullanmış oluruz template argumanı olarak
std::remove_reference_t ise bir type alias.
decltype(*std::declval<T>()) bir reference türü ise onu kaldırır.
*****************************
template <typename T, typename U>
requires requires (T x, U p) {
*p > x;
}
class Data{};
int main()
{}
*****************************
AÇIKLAMA : 2.template parametresi türünden bir nesne dereference edildiğinde 1. template parametresi türünden bir nesne ile karşılaştırılabilir.
*****************************
template <typename T, typename U>
concept cmp = requires (T x, U p) {
*p > x;
};
int main()
{}
*****************************
AÇIKLAMA : concept haline getirdik.
*****************************
template <typename T, typename U>
concept cmp = requires (T x, U y) {
x.foo() || y.bar();
};
struct A {
int foo();
};
struct B {};
int main()
{
constexpr auto val = cmp<A, B>;
std::cout << boolalpha << val;
}
*****************************
AÇIKLAMA : burdaki || farklı anlamdadır. B sınıfının da bar() fonksiyonu olursa true olur.
*****************************
CEVAP : false
*****************************
template <typename T, typename U>
concept cmp = requires (T x, U y) {
x.foo() || y.bar();
};
struct A {
int foo();
};
struct B {
int bar();
};
int main()
{
constexpr auto val = cmp<A, B>;
std::cout << boolalpha << val;
}
*****************************
CEVAP : true
*****************************
amaç bu 2 koşuldan biri sağlanması ise 2 ayrı requires clause yazılmalıdır.
#include <iostream>
#include <concepts>
using namespace std;
template <typename T>
concept has_foo = requires(T x) {
x.foo();
};
template <typename T>
concept has_bar = requires(T x) {
x.bar();
};
template <typename T, typename U>
requires has_foo<T> || has_bar<U>
constexpr bool check_requirements() {
return true;
}
struct A {
int foo();
};
struct B {};
int main()
{
constexpr auto val = check_requirements<A, B>();
std::cout << boolalpha << val;
}
*****************************
CEVAP : true
*****************************
template <typename T, typename U>
concept cmp =
requires (T x) {
x.foo();
} ||
requires (T x) {
x.bar();
};
struct A {
int foo();
};
struct B {};
int main()
{
constexpr auto val = cmp<A, B>;
std::cout << boolalpha << val;
}
*****************************
CEVAP : true
*****************************
doğrudan disable etmek için
template <typename T>
requires false
class Data {
};
int main()
{
Data<int> val;
}
*****************************
AÇIKLAMA : tüm specialization lar devre dışı
error: template constraint failure for ‘template requires false class Data’
*****************************
CEVAP : error
*****************************
requires expression da 3 tane bişelen olabilir.
simple requirement
doğrudan geçerli olması gereken bir ifade yazılabilir.
template <typename T>
concept tmp = requires(T x) {
x.foo();
*x;
};
int main()
{}
type requirement
türün geçerli olması gerekir
typename keyword kullanılır
template <typename T>
requires requires {
typename T::value_type::first_type;
typename T::value_type::second_type;
}
class Data {};
int main()
{}
template <typename T>
concept fs = requires {
typename T::value_type::first_type;
typename T::value_type::second_type;
};
int main()
{}
template <typename T>
requires std::integral<T>
class Data {
};
template <typename T>
concept tmp = requires {
typename Data<T>;
};
int main()
{
}
template <typename T>
concept tmp = requires(typename T::value_type x) {
std::cout << x;
};
using mytpe = std::vector<int>;
int main()
{
static_assert(tmp<mytype>);
}
*****************************
AÇIKLAMA : vector<int> açılımının value_type'ı var ve int türüdür bu da çıkış akımına verilebilir
*****************************
template <typename T>
concept tmp = requires(typename T::value_type x) {
std::cout << x;
};
struct A{};
using mytype = std::vector<A>;
int main()
{
static_assert(tmp<mytype>);
}
*****************************
AÇIKLAMA : çıkış akımına verilemez. << operatoru eksiktir.
*****************************
template <typename T>
concept tmp = requires(typename T::value_type x) {
std::cout << x;
};
struct A {
int val;
};
std::ostream& operator<<(std::ostream& os, const A& a) {
return os << a.val;
}
using mytype = std::vector<A>;
int main()
{
static_assert(tmp<mytype>);
}
compound requirements
template <typename T>
concept tmp = requires(T x) {
{ x.foo() } noexcept; // bu ifadenin yürütülmesi exception throw etmeme garantisi verir.
};
int main()
{}
template <typename T>
concept tmp = requires(T x) {
{ x.foo() };
};
struct A{};
struct B{ void foo(); };
int main()
{
std::cout << tmp<A> << "\n"; // false (A sınıfının foo fonksiyonu olmadığı için
std::cout << tmp<B>; // true
}
template <typename T>
concept tmp = requires(T x) {
{ x.foo() } noexcept;
};
struct B{ void foo(); };
int main()
{
std::cout << tmp<B>; // false
}
*****************************
AÇIKLAMA : struct B içerisinde foo() fonksiyonu tanımlanmış, fakat bu fonksiyonun noexcept olarak belirtilmemiştir.
Yani foo() fonksiyonu, exception garanti etmez, yani exception fırlatabilir.
tmp<B> kullanımı sırasında B türü, foo() fonksiyonunun noexcept olarak tanımlanmadığı için tmp concept'ini geçemez.
*****************************
template <typename T>
concept tmp = requires(T x) {
{ x.foo() } noexcept;
};
struct B {
void foo() noexcept;
};
int main()
{
std::cout << tmp<B>; // true
}
std::same_as
compound requirements geri dönüş türünü elde edebilmek için kullanılır.
x.foo() ifadesinin türü foo() fonksiyonunun geri dönüş türüdür. Derleyici bu ifadenin türünü decltype'a operand yapar.
std::same_as 2 template parametresine sahiptir.
template< class T, class U >
concept same_as = /* see below */;
template <typename T>
concept tmp = requires(T x) {
{ x.foo() } noexcept -> std::same_as<int>;
};
struct B {
void foo() noexcept;
};
int main()
{
std::cout << tmp<B>;
}
*****************************
AÇIKLAMA :
T türünden bir x nesnesi oluşturulabilmeli.
x.foo() çağrılabilmeli.
x.foo() çağrısı noexcept olmalı.
x.foo() fonksiyonunun dönüş tipi tam olarak int olmalı (başka bir tür olamaz).
*****************************
CEVAP : false
*****************************
template <typename T>
concept tmp = requires(T x) {
{ x.foo() } noexcept -> std::same_as<int>;
};
struct B {
int foo();
};
int main()
{
std::cout << tmp<B>;
}
*****************************
CEVAP : false
*****************************
template <typename T>
concept tmp = requires(T x) {
{ x.foo() } noexcept -> std::same_as<int>;
};
struct B {
int foo() noexcept;
};
int main()
{
std::cout << tmp<B>;
}
*****************************
CEVAP : true
*****************************
template <typename T>
concept tmp = requires(T x) {
{ x.foo()} noexcept ->std::integral;
};
struct B{ void foo() noexcept; };
int main()
{
std::cout << tmp<B>;
}
*****************************
AÇIKLAMA : ifadenin türü tam sayı türü olması istenirse std::integral yazılır.
*****************************
CEVAP : false
*****************************
requires clause requires expression'ın öğelerinden biri olabilir.
bu ifade de nested requires olarak tanımlanır.
template <typename T>
concept tmp = requires(T x) {
requires std::is_reference_v<T>;
};
int main()
{
static_assert(tmp<int&>); // ✅ int& referans olduğu için true
static_assert(tmp<const double&>); // ✅ const double& referans olduğu için true
static_assert(tmp<int>); // ❌ int referans değil, false
}
*****************************
AÇIKLAMA :
requires std::is_reference_v<T>; koşulun sağlanması anlamındadır.
std::is_reference_v<T>; şeklinde yazılırsa sadece yazılabilir olduğunu ifade etmek içindir.
*****************************
template <typename T>
concept Pointer = std::is_pointer_v<T>;
template <typename T>
concept Reference = std::is_reference_v<T>;
template <typename T>
concept RefOrPointer = Pointer<T> || Reference<T>;
template <typename T>
concept Pointer = sd::is_pointer_v<T>;
template <typename T>
concept Reference = sd::is_reference_v<T>;
template <typename T>
concept RefOrPointer = requires {
requires Pointer<T> || Reference<T>;
};
common_type HATIRLATMA!
variadic template parametresine sahiptir.
c++11 dile eklenmiştir.
type traits
template< class... T >
struct common_type;
int main()
{
using CommonT = std::common_type_t<int, double, long>;
std::cout << typeid(CommonT).name() << '\n'; // double
}
int main()
{
std::common_type_t<int> x{};
std::cout << typeid(x).name() << '\n';
std::common_type_t<int, long> y{};
std::cout << typeid(y).name() << '\n';
}
int main()
{
std::common_type_t<int, std::string> x{};
}
*****************************
AÇIKLAMA : ortak bir türleri yoksa syntax error
*****************************
CEVAP : syntax error
*****************************
int main()
{
std::common_type_t<const char*, std::string> x{};
}
*****************************
AÇIKLAMA : common_type string oldu.
*****************************
CEVAP : legal
*****************************
karmaşık bir tür beklentisi de olabilir.
template <typename T>
concept Types = requires {
typename T::value_type;
typename T::iterator;
typename T::const_iterator;
typename T::reverse_iterator;
};
int main()
{
static_assert(Types<std::vector<int>>); // ✅ Geçerli (std::vector iç türlere sahip)
static_assert(Types<std::list<double>>); // ✅ Geçerli (std::list iç türlere sahip)
struct A {};
static_assert(Types<Data>); // ❌ Hata: Data struct'ı bu türleri içermiyor.
}
*****************************
AÇIKLAMA : Bu concept'in amacı T türünün belirli iç türlere (nested type) sahip olup olmadığını kontrol etmektir.
T::value_type → İçerdiği öğelerin türü (örneğin, std::vector<int>::value_type → int).
T::iterator → Standart bir iterator türü olup olmadığını kontrol eder.
T::const_iterator → Sabit (const) iterator olup olmadığını kontrol eder.
T::reverse_iterator → Geriye doğru iterator olup olmadığını kontrol eder.
Bu concept, std::vector<T>, std::list<T>, std::deque<T> gibi STL (Standard Template Library) kapsayıcıları için true olacaktır.
*****************************
template <typename T, typename U>
concept tmp = requires {
typename std::common_type_t<T, U>; // typename ile concept değil trait kullanılır.
};
struct Data {};
int main()
{
static_assert(tmp<int, double>); // fail olmaz
static_assert(tmp<const char*, std::string>); // fail olmaz
static_assert(tmp<int, Data>); // fail
}
std::common_with bir concepttir.
template <typename T, typename U>
concept tmp = requires(T x, U y) {
{ x + y } -> std::common_with<U>; // compound req. ile concept kullanarak yazabiliriz.
};
struct Data {};
int main()
{
static_assert(tmp<int, Data>); // fail
}
*****************************
AÇIKLAMA : "x + y" ifadesinin türü U türünden olacak.
{ x + y } -> std::common_with<std::string>; yazarsak "x + y" ifadesinden elde edilecek tür string olmak zorundadır.
*****************************
template argumanı olan türün bir tam sayı türü olması constraint'ini kaç farklı şekilde verebiliriz ?
3 farklı şekilde gösterebiliriz. (daha fazla da olabilir !!!)
template <typename T>
concept c1 = std::integral<T>;
int main()
{
std::cout << c1<double>; // false
}
template <typename T>
concept c2 = requires {
requires std::integral<T>;
};
int main()
{
std::cout << c2<double>;
}
*****************************
AÇIKLAMA : nested requirement ile
*****************************
CEVAP : false
*****************************
template <std::integral T>
class Data;
template <typename T>
concept c3 = requires {
typename Data<T>; // bunun sağlanması için template argumanı tam sayı türü olmalıdır.
};
int main()
{
std::cout << c3<double>;
}
*****************************
AÇIKLAMA : sınıf şablonu ile type requirement kullanılarak.
Data sınıf şablonu constraint
*****************************
CEVAP : false
*****************************
Örnek constraint
*p , p[]
template <typename T>
concept tmp = requires (T p) {
*p;
p[0];
};
template <typename T>
concept tmp = requires (T p) {
*p || p[0]; // aşağıdaki örnekle aynı anlama gelmez.
};
template <typename T>
concept Dereferencable = requires (T p) {
*p;
};
template <typename T>
concept Subscriptable = requires (T p) {
p[0];
};
template <typename T>
concept Cmp = Dereferencable<T> || Subscriptable<T>;
template <typename T>
void foo(T y)
{
std::hash<T> val;
}
int main()
{
foo(10); // legal
}
*****************************
AÇIKLAMA : çünkü `std::hash<int>` tanımlıdır.
ancak std::vector ya da std::unique_ptr icin tanımlı değildir.
*****************************
template <typename T>
void foo(T y)
{
std::hash<T> val;
}
struct Data {};
int main()
{
foo(Data{});
}
*****************************
AÇIKLAMA : hash<Data> açılımı olmadığı için
*****************************
CEVAP : syntax error
*****************************
template <typename T>
void foo(T y)
{
std::hash<T> val;
}
struct Data {};
template<>
struct std::hash<Data> {
std::size_t operator()(const Data&) const;
};
int main()
{
foo(Data{}); // legal
}
Yukarıdaki template constraint edilmek istenirse...
template <typename T>
concept shash = requires {
typename std::hash<T>;
};
void foo(shash auto){}
struct Data {};
int main()
{
foo(Data{}); // legal
}
*****************************
AÇIKLAMA : normalde hata olmalıdır.
typename std::hash<T>; böyle bir tür oluşturulabilir mi anlamında kullanıldı çünkü.
*****************************
template <typename T>
concept shash = requires {
std::hash<T>;
};
void foo(shash auto){}
struct Data {};
int main()
{
foo(Data{}); // error
}
Kod | Çalışır mı? | Açıklama |
std::hash<T>; | ❌ | std::hash<T> bir türdür, ancak requires içinde doğrudan bir ifade gibi kullanılamaz. |
typename std::hash<T>; | ✅ | std::hash<T>'nin geçerli bir tür olup olmadığını kontrol eder. |
📌 Genel Kural: requires bloğunda bir türü test etmek için typename kullanılmalıdır.
📌 Eğer typename yazmazsanız, std::hash<T> bir tür yerine bir ifade olarak değerlendirilmeye çalışılır ve hata oluşur.
template <typename T>
concept shash = requires {
std::hash<T>{};
};
void foo(shash auto){}
struct Data {};
template<>
struct std::hash<Data> {
std::size_t operator()(const Data&) const;
};
int main()
{
foo(Data{});
}
*****************************
AÇIKLAMA : std::hash<Data> için bir explicit specialization
*****************************
CEVAP : legal
*****************************
concept kullanım senaryoları.
template <typename T>
concept hasfoo = requires (T x) {
{ x.foo() }noexcept -> std::convertible_to<bool>;
};
struct Data {
int foo() const noexcept;
};
int main()
{
std::cout << hasfoo<Data>;
}
*****************************
AÇIKLAMA : foo fonksiyonu olacak ve geri dönüş değeri türü bool türüne dönüştürülebilir olacaktır.
*****************************
CEVAP : true
*****************************
template <typename T>
concept hasfoo = requires (T x) {
{ x.foo() }noexcept -> std::same_as<bool>;
};
struct A {
bool foo() noexcept { return true; } // ✅ `bool` döndürüyor ve `noexcept`
};
struct B {
int foo() noexcept { return 42; } // ❌ `int` döndürüyor, konsepti sağlamaz
};
struct C {
bool foo() { return true; } // ❌ `noexcept` değil, konsepti sağlamaz
};
int main() {
std::cout << hasfoo<A> << "\n"; // ✅ true
std::cout << hasfoo<B> << "\n"; // ❌ false
std::cout << hasfoo<C> << "\n"; // ❌ false
}
*****************************
AÇIKLAMA : foo fonksiyonu olacak ve geri dönüş değeri türü bool olmak zorunda demektir.
*****************************
template <typename T>
concept HasFB = requires (T x) {
{ x.foo() }noexcept -> std::convertible_to<bool>;
{ x.bar() }noexcept -> std::same_as<bool>;
};
template <typename T>
requires HasFB<T>
class Data {
public:
void res() { std::cout << "Valid type!\n"; }
};
struct ValidType {
int foo() noexcept { return 42; } // ✅ `int` -> `bool` dönüşümü mümkün
bool bar() noexcept { return true; } // ✅ `bool` döndürüyor
};
struct InvalidType1 {
bool foo() noexcept { return true; } // ✅ `bool` döndürüyor (convertible to `bool`)
int bar() noexcept { return 42; } // ❌ `int` yerine `bool` olmalı
};
struct InvalidType2 {
int foo() { return 42; } // ❌ `noexcept` eksik
bool bar() noexcept { return true; } // ✅
};
int main()
{
Data<ValidType> d1; // ✅ Geçerli
d1.res();
// Data<InvalidType1> d2; // ❌ (bar() `bool` değil)
// Data<InvalidType2> d3; // ❌ (foo() noexcept değil)
}
template <typename T>
concept HasFB = requires (T x) {
{ x.foo() }noexcept -> std::convertible_to<bool>;
{ x.bar() }noexcept -> std::same_as<bool>;
};
template <typename T>
requires HasFB<T>
T f1(T x);
template <typename T>
concept HasFB = requires (T x) {
{ x.foo() }noexcept -> std::convertible_to<bool>;
{ x.bar() }noexcept -> std::same_as<bool>;
};
template <typename T>
T f2(T x) requires HasFB<T>;
*****************************
AÇIKLAMA : trailing requires clause
parametre değişkenlerini kullanmak avantajdır burda...
*****************************
template <typename T>
concept HasFB = requires (T x) {
{ x.foo() }noexcept -> std::convertible_to<bool>;
{ x.bar() }noexcept -> std::same_as<bool>;
};
template <typename T>
concept tmp = std::integral<T> || HasFB<T>;
*****************************
AÇIKLAMA : overloading tarafında kullanışlıdır.
*****************************
constrained template parameter
typename yerine concept ismini yazma durumudur.
template <typename T>
concept HasFB = requires (T x) {
{ x.foo() }noexcept -> std::convertible_to<bool>;
{ x.bar() }noexcept -> std::same_as<bool>;
};
template <typename T>
requires HasFB<T>
class Data {};
yukarıdaki kod ile aynı anlamdadır yani typename yerine concept adı geçilmiştir.
template <typename T>
concept HasFB = requires (T x) {
{ x.foo() }noexcept -> std::convertible_to<bool>;
{ x.bar() }noexcept -> std::same_as<bool>;
};
template <HasFB T>
class Data {};
Function template olması durumunda abbreviated function template ile de yazabiliriz. (auto ile)
template <typename T>
concept HasFB = requires (T x) {
{ x.foo() }noexcept -> std::convertible_to<bool>;
{ x.bar() }noexcept -> std::same_as<bool>;
};
template <HasFB T>
void fun(T x);
template <typename T>
concept HasFB = requires (T x) {
{ x.foo() }noexcept -> std::convertible_to<bool>;
{ x.bar() }noexcept -> std::same_as<bool>;
};
void fun(HasFB auto x);
local düzeyde template oluşturulamaz.
int main()
{
template <typename T>
concept tmp = std::integral<T>;
}
*****************************
AÇIKLAMA : a template declaration cannot appear at block scope
*****************************
CEVAP : syntax error
*****************************
int main()
{
auto fn = [] (std::integral auto v) {
return v * 10;
};
std::cout << fn(10);
}
*****************************
AÇIKLAMA : fn ye gecilen 10 int olduğu icin geçerlidir.
*****************************
CEVAP : 100
*****************************
int main()
{
auto fn = [] (std::integral auto v) {
return v * 10;
};
std::cout << fn(10.5);
}
*****************************
AÇIKLAMA : fn ye gecilen 10.5 int olmadıgı icin geçerli degildir.
*****************************
CEVAP : candidate: ‘template requires integral main()::)>’
constraints not satisfied
*****************************
int main()
{
auto fn = [] (std::integral auto v) -> std::integral auto {
return v * 10;
};
std::cout << fn(4);
}
*****************************
CEVAP : 40
*****************************
template <typename T>
concept tmp = requires {
requires std::integral<T>;
};
template <auto n>
concept Less10 = n < 10;
template <auto T>
requires Less10<T>
class Data {};
int main()
{
Data<8> d; // legal
Data<15> d; // syntax error
}
Bir fonksiyonun sadece belirli türden argumanla çağırılmasına izin vermek ?
template <typename T>
void func(T) = delete;
void func(int){}
int main()
{
func(10);
func(5.4); // error: use of deleted function ‘void func(T) [with T = double]’
}
*****************************
AÇIKLAMA : concepts kullanmadan
*****************************
CEVAP :
*****************************
template <typename T, std::enable_if<std::is_same_v<T, int>> * = nullptr>
void func(T){}
int main()
{
func(10);
func(5.4); // syntax error
}
*****************************
AÇIKLAMA :
T == int ise, std::enable_if_t<true> sonucu void olur ve func şablonu etkinleştirilir.
T != int ise, std::enable_if_t<false> derleme hatası üretir ve fonksiyon devre dışı bırakılır.
SFINAE kullanarak...
*****************************
template <typename T>
requires std::same_as<T, int>
void func(T){}
int main()
{
func(10);
func(5.4); // syntax error
}
*****************************
AÇIKLAMA : c++20 concept ile
*****************************
void func(std::same_as<int> auto){}
int main()
{
func(10);
func(5.4); // syntax error
}
*****************************
AÇIKLAMA : abbreviated syntax ile
*****************************
std::invocable concept
variadic parametre paketine sahip bir concept
ilk parametre callable, sonrası variadic
gönderilecek olan callable gönderilen tür ile çağırılabilir demektir.
template <typename F, typename... Args>
concept invocable =
requires(F&& f, Args&&... args) {
std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
/* not required to be equality-preserving */
};
void func(std::invocable<int> auto fn, int x)
{
fn(x);
}
void func(std::invocable<int> auto fn)
{
fn(10);
}
int main()
{
func([](int x){ return x * x; }); // legal
func([](std::string x){ return x + x; }); // syntax error
}
void func(std::invocable<int, double> auto fn, int x)
{
std::cout << fn(x, x);
}
int main()
{
func([](int x, double d){ return x * d; }, 20);
}
*****************************
CEVAP : 400
*****************************
std::regular concept
CopyConstructible, CopyAssignable, Destructible, DefaultConstructible, EqualityComparable olmalıdır.
c++20
template< class T >
concept regular = std::semiregular<T> && std::equality_comparable<T>;
#include <concepts>
#include <iostream>
template<std::regular T>
struct Single
{
T value;
friend bool operator==(const Single&, const Single&) = default;
};
int main()
{
Single<int> myInt1{4};
Single<int> myInt2;
myInt2 = myInt1;
if (myInt1 == myInt2)
std::cout << "Equal\n";
std::cout << myInt1.value << ' ' << myInt2.value << '\n';
}
*****************************
AÇIKLAMA :
*****************************
CEVAP :
Equal
4 4
*****************************
void func(std::invocable auto fn) // ✅ Çağrılabilir (invocable) türleri kabul eder
{
fn();
}
int main()
{
func([]{ std::cout << "std::regular used...!\n"; }); // ✅ Lambda
func(15); // ❌ 15() geçersiz.
func(std::string{"smartcode"}); // ❌ std::string() fonksiyon değil.
}
void func(std::regular auto){}
struct Data {};
int main()
{
func(Data{});
}
*****************************
AÇIKLAMA : Data structure hiçbir özel üye fonksiyon (kopyalama, taşıma, atama, karşılaştırma) içermiyor
*****************************
CEVAP : syntax error
*****************************
void func(std::regular auto){}
struct Data {
bool operator==(const Data&) const = default;
};
int main()
{
func(Data{});
}
*****************************
CEVAP : legal
*****************************
std::copyable
// (since C++20)
template <typename T>
concept copyable =
std::copy_constructible<T> &&
std::movable<T> &&
std::assignable_from<T&, T&> &&
std::assignable_from<T&, const T&> &&
std::assignable_from<T&, const T>;
void func(std::copyable auto){}
struct Data {
Data(const Data&) = delete;
};
int main()
{
func(Data{});
}
*****************************
AÇIKLAMA : no matching function for call to ‘Data::Data()’
*****************************
CEVAP : syntax error
*****************************
void func(std::copyable auto){}
struct Data {};
int main()
{
func(Data{});
}
*****************************
AÇIKLAMA : derleyici özel üye fonksiyonları yazdığı için
*****************************
CEVAP : legal
*****************************
template <std::copyable T>
class Data {};
struct A{};
struct B {
B() = default;
B(const B&) = default;
B& operator=(const B&) = default;
};
struct C {
C() = default;
C(const C&) = delete;
C& operator=(const C&) = delete;
};
struct D {
D() = default;
D(const D&) = default;
D& operator=(const D&) = default;
D(D&&) = delete;
D& operator=(D&&) = delete;
};
int main()
{
Data<int> m1; // legal
Data<A> m2; // legal
Data<B> m3; // legal
Data<C> m4; // syntax error
Data<D> m5; // syntax error
}
std::semiregular
template <typename T>
concept semiregular = std::copyable<T> && std::default_initializable<T>;
void func(std::semiregular auto){}
struct Data {};
int main()
{
func(15);
func(std::string{"smartcode"});
func(Data{});
}
*****************************
AÇIKLAMA : std::semiregular concept, std::regular ile çok benzer olup, sadece eşitlik karşılaştırmasını (operator==) zorunlu kılmaz.
*****************************
CEVAP : legal
*****************************
std::totally_ordered<T>
Guarantees that objects of type T are comparable with the operators ==, !=, <, <=, >, and >= so that two values are always either equal to, or less than, or greater than each other.
The concept does not claim that type T has a total order for all values. In fact, the expression std::totally_ordered<double> yields true even though floating-point values do not have a total order:
std::totally_ordered<double> // true
std::totally_ordered<std::pair<double, double>> // true
std::totally_ordered<std::complex<int>> // false
This concept is therefore provided to check the formal requirements to be able to sort elements.
It is used by std::ranges::less, which is the default sorting criterion for sorting algorithms.
That way, sorting algorithms do not fail to compile if types do not have a total order for all values.
Supporting all six basic comparison operators is enough.
The values to be sorted should have a total or weak order.
However, this a semantic constraint that cannot be checked at compile time.
Requires:
std::equality_comparable<T> is satisfied
All comparisons with the operators ==, !=, <, <=, >, and >= yield a value convertible to bool
std::totally_ordered_with<T1, T2>
Guarantees that objects of types T1 and T2 are comparable with the operators ==, !=, <, <=, >, and >= so that two values are always either equal to, or less than, or greater than each other.
Requires:
==, !=, <, <=, >, and >= are supported for all comparisons where objects of T1 and/or T2 are involved and yield a value of the same type convertible to bool
std::totally_ordered concept
karşılaştırma işlemleri yapılabilecek
std::totally_ordered_with concept
tüm karşılaştırma işlemleri yapılabilecek
void func(std::totally_ordered_with<int> auto){}
int main()
{
func(15);
}
*****************************
AÇIKLAMA : int ile tüm karşılaştırma için kısıtlama
a < b, a > b
a <= b, a >= b
a == b, a != b
*****************************
CEVAP : legal
*****************************
void func(std::totally_ordered_with<int> auto){}
struct Data {};
int main()
{
func(Data{});
}
*****************************
AÇIKLAMA : int ve Data karşılaştırma hatası alırız.
*****************************
CEVAP : syntax error
*****************************
void foo(std::totally_ordered auto x){}
struct A {};
struct B {
auto operator<=>(const B& rhs) const = default;
};
struct C {
bool operator==(const C&) const;
};
int main()
{
foo(1); // legal
foo(string{"ali"}); // legal
//foo(A{}); // syntax error
foo(B{}); // legal
//foo(C{}); // syntax error
}
*****************************
AÇIKLAMA : space_ship operatoru (<=>) ileride ayrı başlıkta incelenecektir.
*****************************
concept kullanmanın ilave avantajlarından biri de overloading.
subsumption
void foo(std::integral auto)
{
std::cout << "integral\n";
}
void foo(std::unsigned_integral auto)
{
std::cout << "unsigned integral\n";
}
int main()
{
foo(15); // integral
foo(15U); // unsigned integral
}
*****************************
AÇIKLAMA : 2. fonksiyon olmasa foo(15U) çağrısında integral olan çağırılabilir.
Ancak bu durumda subsumption durumu geçekleşir. 2. concept ilk concept'i subsume eder.
*****************************
template <typename T>
concept sub = requires (T x) {
x.foo();
};
template <typename T>
concept con = sub<T> && requires (T x) {
x.bar();
};
void func(sub auto)
{
cout << "sub auto\n";
}
void func(con auto)
{
cout << "con auto\n";
}
struct A {
void foo();
};
struct B {
void foo();
void bar();
};
int main()
{
func(A{}); // sub auto
func(B{}); // con auto
}
template <typename T>
concept sub = requires (T x) {
x.foo();
};
template <typename T>
concept con = sub<T> || requires (T x) {
x.bar();
};
void func(sub auto)
{
cout << "sub auto\n";
}
void func(con auto)
{
cout << "con auto\n";
}
struct A {
void foo();
};
struct B {
void foo();
void bar();
};
int main()
{
func(B{}); // sub auto
}
*****************************
AÇIKLAMA : daha kısıtlayıcı olan çağırılır.
*****************************
Bunu sağlayabilmek için concept kullanmak zorunludur.
Bunu göstermek için requires clause ya da requires expression ile örnek verelim.
template <typename T>
concept integral_or_floating = std::integral<T> || std::floating_point<T>;
template <typename T>
concept integral_and_char = std::integral<T> && std::same_as<T, char>;
void func(integral_and_char auto)
{
cout << "std::same_as<char>\n";
}
void func(std::integral auto)
{
cout << "std::integral\n";
}
void func(integral_or_floating auto)
{
cout << "integral or floating\n";
}
int main()
{
func(5);
func(5.4);
func('a');
}
*****************************
CEVAP :
std::integral
integral or floating
std::same_as<char>
*****************************
std::indirect_unary_predicate
F, tek argümanlı bir fonksiyon/fonksiyon nesnesi olmalı (unary predicate).
I, bir iterator türü olmalı.
F, Iter’in işaret ettiği türdeki bir nesneye uygulanabilmeli.
template <class F, class I>
concept indirect_unary_predicate =
std::indirectly_readable<I> &&
std::copy_constructible<F> &&
std::predicate<F&, /*indirect-value-t*/<I>> &&
std::predicate<F&, std::iter_reference_t<I>>;
template <typename T, typename I>
void bar(T fn, I iter)
requires std::indirect_unary_predicate<T, I> // trailing requires clause
{
cout << fn(*iter) << "\n";
}
int main()
{
// lambda ifadesi ile bir clauser object oluşturuldu. int arg ile çağırılabilen bir fonk.
const auto pred = [](int x){ return x % 5 == 0; };
std::vector ivec{ 4, 50, 12, 20, 35, 7, 9, 40 };
boolalpha(cout);
for (auto iter = ivec.begin(); iter != ivec.end(); ++iter)
{
bar(pred, iter);
}
}
*****************************
AÇIKLAMA : T callable iter dereference edildiğinde dereference edilmiş iter ifadesiyle çağırılabilmelidir.
*****************************
CEVAP :
false
true
false
true
true
false
false
true
*****************************
Comments