Traits trong C++: Cơ chế xác định và biến đổi kiểu tại thời điểm biên dịch

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ồm bool, 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ề true nế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-qualifier
  • add_reference: thêm & hoặc && tùy ngữ cảnh
  • decay: 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_typeintegral_constant<bool, true>
  • std::false_typeintegral_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 constexpris_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
}

Thẻ: cpp type-traits metaprogramming Template SFINAE

Đăng vào ngày 21 tháng 6 lúc 22:38