Các trait trong C++ là một tập hợp các template lớp được thiết kế đặc biệt để kiểm tra, trích xuất hoặc biến đổi thuộc tính của kiểu dữ liệu ngay trong quá trình biên dịch. Thư viện chuẩn cung cấp header <type_traits>, nơi định nghĩa hàng loạt cơ chế mạnh mẽ giúp lập trình viên thực hiện phân tích tĩnh về kiểu — từ việc xác định bản chất (số nguyên, con trỏ, lớp…) đến việc tạo ra kiểu mới thông qua các phép biến đổi như loại bỏ const, thêm tham chiếu, hay "giảm bậc" mảng thành con trỏ.
Kiểm tra thuộc tính kiểu
Các hàm kiểm tra (predicate traits) thường bắt đầu bằng tiền tố is_ và trả về giá trị hằng số tĩnh value kiểu bool. Từ C++17, phiên bản rút gọn với hậu tố _v được cung cấp để tránh viết lặp ::value.
static_assert(std::is_integral_v<short>); // true
static_assert(!std::is_floating_point_v<unsigned long>); // true
static_assert(std::is_class_v<std::string>); // true
static_assert(std::is_same_v<char, signed char>); // false
Một số trait phổ biến:
is_integral: xác định kiểu có phải là số nguyên (bao gồmbool,char,int, v.v.)is_floating_point: kiểm tra kiểu dấu phẩy động (float,double,long double)is_class: trả vềtruenếu kiểu là lớp hoặc cấu trúc (không bao gồm union)is_same: so sánh hai kiểu có trùng khớp hoàn toàn không
Biến đổi kiểu
Các transformation traits không thay đổi kiểu gốc mà sinh ra kiểu mới dựa trên quy tắc đã định. Kết quả được truy cập qua thành viên kiểu type, hoặc dạng rút gọn _t từ C++14.
static_assert(std::is_same_v<std::remove_const_t<const volatile int>, int>);
static_assert(std::is_same_v<std::add_lvalue_reference_t<double>, double&>);
static_assert(std::is_same_v<std::add_pointer_t<char>, char*>);
static_assert(std::is_same_v<std::decay_t<int[3][4]>, int(*)[4]>);
Một số phép biến đổi tiêu biểu:
remove_const/remove_volatile: gỡ bỏ các bộ định danh cv-qualifieradd_reference: thêm&hoặc&&tùy ngữ cảnhdecay: mô phỏng hành vi truyền tham số theo giá trị — chuyển mảng sang con trỏ, hàm sang con trỏ hàm, loại bỏ cv-qualifier và tham chiếu
Đóng gói giá trị thành kiểu
Lớp mẫu std::integral_constant cho phép nâng một giá trị hằng số biên dịch lên thành một kiểu riêng biệt, mở đường cho kỹ thuật lập trình hướng kiểu (type-level programming). Mỗi thể hiện là một kiểu duy nhất, phân biệt bởi giá trị được lưu trữ.
using CountThree = std::integral_constant<size_t, 3>;
using CountSix = std::integral_constant<size_t, 6>;
static_assert(CountThree::value + CountThree::value == CountSix::value);
Hai kiểu đặc biệt được định nghĩa sẵn:
std::true_type→integral_constant<bool, true>std::false_type→integral_constant<bool, false>
Điều khiển khả năng khai triển mẫu với enable_if
std::enable_if là công cụ then chốt hỗ trợ SFINAE (Substitution Failure Is Not An Error), cho phép loại bỏ các khai báo mẫu không phù hợp dựa trên điều kiện kiểu. Nếu điều kiện sai, thành viên type sẽ không tồn tại — dẫn đến lỗi thay thế bị bỏ qua thay vì gây lỗi biên dịch.
template<typename T>
std::enable_if_t<std::is_arithmetic_v<T>, T>
compute_square(T x) {
return x * x;
}
template<typename T>
std::enable_if_t<!std::is_arithmetic_v<T>>
compute_square(const T&) {
static_assert(sizeof(T) == 0, "Type must be arithmetic");
}
Với C++20, có thể thay thế bằng constrained templates sử dụng requires, nhưng enable_if vẫn giữ vai trò quan trọng trong các thư viện đa nền tảng hoặc khi cần tương thích ngược.
Ứng dụng thực tế
Dưới đây là ví dụ minh họa cách kết hợp if constexpr và is_constructible để chọn hành vi khác nhau tùy vào khả năng xây dựng đối tượng:
#include <iostream>
#include <type_traits>
template<typename T>
void describe_construction() {
if constexpr (std::is_default_constructible_v<T>) {
std::cout << "Type supports default construction.\n";
} else if constexpr (std::is_trivially_constructible_v<T>) {
std::cout << "Type has trivial default constructor.\n";
} else {
std::cout << "Default construction not available.\n";
}
}
struct Trivial {};
struct NonTrivial { NonTrivial() {} };
int main() {
describe_construction<int>(); // Default construction
describe_construction<Trivial>(); // Trivial
describe_construction<NonTrivial>(); // Not available
}