C++11 cho phép lập trình viên tự tạo literal cho mình khi mà các bạn cảm thấy các literal sẵn có không đủ. Trong bài viết này mình sẽ làm 3 ví dụ về 3 loại literal thường gặp khi học lập trình là:

- Literal số nguyên (interger literal)
- Literal số thực (foatingpoint literal)
- Literal chuỗi (string literal)

Mục tiêu đặt ra: Ta sẽ khiến cho 3 dòng code sau hoạt động được với compiler C++11:

Visual C++ Code:
  1. //Tạo 1 điểm tọa độ M(x,y) bất kỳ thuộc đường thẳng y = x - 3 với x,y là các số nguyên.
  2. intPoint2 m = -2yEqXminus3; // M(-2, -5)
  3.  
  4. // Tạo 1 điểm N(x,y) bất kỳ thuộc đường cong y = lnx với x,y là các số thực.
  5. floatPoint2 n = 2.0yEqLogaNapierX; // N(2, ln(2))
  6.  
  7. // Tạo 1 chuỗi toàn các ký tự viết thường bất chấp mọi ký tự in hoa bạn nhập vào trong chuỗi
  8. // Nếu có chữ in hoa, chúng tự động chuyển sang chữ thường.
  9. std::string ignoreCaseStr = "The Quick Brown Fox Jumps over the LAZY Dog."_ignoreCase;
Trước tiên là khai báo literal riêng của chúng ta, trong đó ta sẽ sử dụng 2 class intPoint2 và floatPoint2 để biểu diễn dữ liệu cho 2 điểm M và N:

Visual C++ Code:
  1. intPoint2 operator"" yEqXminus3(unsigned long long wLit); // y equals x-3.
  2. floatPoint2 operator"" yEqLogaNapierX(long double wLit); // y equals loga-Napier of x.
  3. std::string operator"" ignoreCase(const char* wLit, size_t wSize);
Ba đoạn code trên khai báo 3 literals với tên là yEqXminus3, yEqLogaNapierX và ignoreCase, lần lượt là 3 literal mà chúng ta cần.
Từ khóa operator"" cho biết toán tử này sẽ được gọi khi compiler tìm thấy tên literal mà chúng ta tạo.
Ở đây chúng ta thấy tham số truyền vào cho literal số nguyên của mình là kiểu không dấu (unsigned), điều đó có nghĩa là điểm M(-2, -5) với tọa độ âm sẽ biến literal của chúng ta thành trò hề.
Rất tiếc là tại thời điểm mình viết bài này lập trình viên không được phép truyền giá trị âm cho literal. Do vậy chúng ta sẽ phải "chơi bẩn" bằng cách bảo literal thông báo cho class biết và để class tự xử lý dữ liệu âm.

Và đây là 2 class biểu diễn dữ liệu với điểm M:
Visual C++ Code:
  1. class intPoint2
  2. {
  3. public: // Ta sẽ public hết cho đơn giản nhé.
  4.  
  5.     int x=0,y=0;
  6.     int yFromLit;
  7.     bool createFromLit = false;
  8.  
  9.     intPoint2(int x, int y, bool createFromLit = false, int yFromLit = 0)
  10.     {
  11.         this->x = x;
  12.         this->y = y;
  13.         this->createFromLit = createFromLit;
  14.         this->yFromLit = yFromLit;
  15.     }
  16.     intPoint2& operator-()
  17.     {
  18.         x = -x;
  19.         if (createFromLit) y = -yFromLit;
  20.         else y = -y;
  21.         createFromLit = false;
  22.         return *this;
  23.     }
  24. };
Thứ đáng chú ý ở đây là toán tử đảo dấu operator-() và 2 biến yFromLit, createFromLit. Đây chính là công cụ giúp ta xử lý dữ liệu âm khi mà literal chỉ chấp nhận dữ liệu không âm.
Quay lại với mục tiêu ta đặt ra ban đầu, đó là làm sao để compiler hiểu được -2yEqXminus3 có nghĩa là điểm M có tọa độ x = -2 và thuộc đường thẳng có phương trình y = x -3. Nếu ta bỏ dấu trừ kia đi, ta sẽ không cần đến 2 biến cũng như cái toán tử kia nữa, cay đắng thay, chúng ta lại cần chúng :v.
Vậy thì ta sẽ làm cho dấu trừ kia hoạt động. Khi thấy dấu trừ đó, compiler sẽ tìm toán tử đảo dấu trong class intPoint2, và sẽ báo lỗi không tìm thấy nếu chúng ta không tạo ra nó, đó là lý do nó tồn tại ở đây.
Trong toán tử operator-() ta sẽ căn cứ vào giá trị createFromLit và quyết định xem sẽ thay đổi y dựa trên chính nó hay dựa trên giá trị yFromLit mà literal truyền vào:
Visual C++ Code:
  1. if (createFromLit) y = -yFromLit;
  2.         else y = -y;

Và giờ, 2 biến chết tiệt kia, ta sẽ dùng đến chúng bằng cách sử dụng constructor với 2 biến được thiết lập mặc định, và sẽ thay đổi khi chúng được tạo bởi literal:
Visual C++ Code:
  1. intPoint2(int x, int y, bool createFromLit = false, int yFromLit = 0)

Rồi, và giờ ta sẽ định nghĩa literal. Đơn giản là ta chỉ việc gọi constructor đã định nghĩa ở trên, lần này ta sẽ đỗi createFromLit sang true, và truyền yFromLit vào để class căn cứ vào đó thay đổi giá trị y cho phù hợp:
Visual C++ Code:
  1. intPoint2 operator"" yEqXminus3(unsigned long long wLit) // y equals x-3.
  2. {
  3.     return intPoint2((int)wLit, (int)wLit - 3, true, (int)(wLit + 3));
  4. }
Trong đoạn code trên, wLit+3 chính là giá trị đúng khi ta tạo điểm M với tọa độ âm, wLit-3 vẫn sẽ được đưa vào nhưng khi đó toán tử đảo dấu sẽ làm nốt việc còn lại.


Và bây giờ, chúng ta sẽ vò đầu bứt tai tiếp với việc tạo literal thứ 2 cho điểm N. Cũng tương tự như trên, nhưng lần này ta chỉ cần mỗi biến bool createFromLit:
Visual C++ Code:
  1. class floatPoint2
  2. {
  3. public:
  4.     bool createFromLit = false;
  5.  
  6.     floatPoint2(float x, float y, bool createFromLit = false)
  7.     {
  8.         this->x = x;
  9.         this->y = y;
  10.         this->createFromLit = createFromLit;
  11.     }
  12.  
  13.     floatPoint2& operator-()
  14.     {
  15.         if (createFromLit) throw - 1;
  16.     }
  17.  
  18.     float x=0,y=0;
  19. };
Vì hàm loga Napier có tập xác định không âm, nên nếu gặp x âm ta sẽ quăng ngoại lệ cho chết cả làng luôn =))
Và ta định nghĩa literal cho điểm N như dưới đây:
Visual C++ Code:
  1. floatPoint2 operator"" _yEqLogaNapierX(long double wLit) // y equals loga-Napier of x.
  2. {
  3.     return floatPoint2( (float)wLit, (float)std::log(wLit), true };
  4. }
Phù... và cuối cùng là cái literal thứ 3, dễ hơn hẳn 2 cái literal trên, vì class std::string đã có sẵn nên ta chỉ phải định nghĩa literal:
Visual C++ Code:
  1. std::string operator"" _ignoreCase(const char* wLit, size_t wSize)
  2. {
  3.     std::string result(wLit, wSize);
  4.     std::transform(result.begin(), result.end(), result.begin(), tolower);
  5.     return result;
  6. }
Phần định nghĩa là mình copy từ stackoverflow nên chả biết nó hoạt động thế nào, các bạn đừng có hỏi nha! =))


OK! Và cuối cùng của cuối cùng, là 1 hàm main để test nếu các bạn lười viết:
Visual C++ Code:
  1. int main()
  2. {
  3.     intPoint2 intVec(-2yEqXminus3);
  4.     std::cout << "Tren duong thang y = x - 3, voi x = " << intVec.x << " thi y = " << intVec.y << std::endl;
  5.  
  6.     floatPoint2 floatVec = 2.0_yEqLogaNapierX;
  7.     std::cout << "Tren duong cong  y= lnx, voi x = " << floatVec.x << " thi y = " << floatVec.y << std::endl;
  8.  
  9.     std::string str = "The Quick Brown Fox Jumps over the LAZY Dog.";
  10.     std::string ignoreCaseStr = "The Quick Brown Fox Jumps over the LAZY Dog."_ignoreCase;
  11.     std::cout << "Chuoi ban dau: " << str << std::endl;
  12.     std::cout << "Chuoi khong phan biet chu hoa chu thuong: " << ignoreCaseStr << std::endl;
  13.  
  14.     std::cin.get();
  15.     return 0;
  16. }

Ps. Code viết khi đang phê cần nên sẽ rất nhiều bug, cần phải cải thiện thêm... :3