C++ 用戶定義字面量 User-defined literals


百度百科:

在計算機科學中,字面量(literal)是用於表達源代碼中一個固定值的表示法(notation)

C++ 中,字面量分為以下幾種:

  • 整型字面量 integer literal
  • 浮點型字面量 floating-point literal
  • 布爾型字面量 boolean literal
  • 字符字面量 character literal
  • 字符串字面量 string literal
  • 用戶定義字面量 user-defined literal

本文暫只對用戶定義字面量進行討論。


用戶定義字面量 User-defined literal

(從 C++11 開始啟用)

C++ 允許我們定義一個用戶定義后綴(user-defined suffix)來把原生的整型浮點型字符字符串 4 種字面量轉換為我們自己需要的形式,這個形式可以是原生數據類型,也可以是自定義類。

具體地,字面量會自動調用一個函數來轉換它的類型。由用戶定義的字面量調用的函數稱為字面量運算符(或者,如果它是模板,則稱為字面量運算符模板)。

定義語法

字面量運算符的函數名以 operator"" 開頭,后面緊跟用戶定義后綴

返回值類型 operator"" 自定義后綴 (參數);

如果字面量運算符是一個模板,它必須有一個空的參數列表。並且只能有一個模板參數,這個模板參數必須是一個元素類型為 char 的非類型模板參數包(a non-type template parameter pack with element type char)。在這種情況下它被稱為數字字面量運算符模板(numeric literal operator template)

template <char...> 返回值類型 operator"" 自定義后綴 ();

注意,其中的自定義后綴 必須以下划線 _ 開頭,並符合標識符命名規范。

一些事實

其實“以下划線開頭”並非是從語法上強制性的。
如果你堅持不以下划線開頭,那么編譯器會給你一個警告:
[Warning] literal operator suffixes not preceded by '_' are reserved for future standardization [-Wliteral-suffix]
但是畢竟只是一個“警告”。


另外,由於字面量的特殊語法結構,自己定義的這個后綴其實可以同時是 C++ 關鍵字而不產生沖突,這是合法的。
如 C++ 標准庫 <complex> 中就定義了 if 來把整形和浮點型轉換為 complex<float>

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wliteral-suffix"

  constexpr std::complex<float>
  operator""if(long double __num)
  { return std::complex<float>{0.0F, static_cast<float>(__num)}; }
  
  constexpr std::complex<float>
  operator""if(unsigned long long __num)
  { return std::complex<float>{0.0F, static_cast<float>(__num)}; }
  
#pragma GCC diagnostic pop

(同時使用了預編譯指令來忽略剛才所說的警告)



關於參數列表:

對於字面量運算符的參數, C++ 有語法上嚴格的規定。參數列表僅允許以下幾種類型:

Parameter Lists Details
1 (const char *) 原始字面量運算符(raw literal operators),用於整數和浮點用戶定義字面量的后備方式
2 (unsigned long long int) 用戶定義整型字面量運算符的首選方式
3 (long double) 用戶定義浮點型字面量運算符的首選方式
4 (char) 通過用戶定義字符型字面量調用
(user-defined character literals)
5 (wchar_t)
6 (char8_t)
7 (char16_t)
8 (char32_t)
9 (const char *, std::size_t) 通過用戶定義字符串字面量調用
(user-defined string literals)
其中第二個參數被自動傳入字符串長度
10 (const wchar_t *, std::size_t)
11 (const char8_t *, std::size_t)
12 (const char16_t *, std::size_t)
13 (const char32_t *, std::size_t)
備注 注意char8_t從 C++20 開始啟用
不允許使用默認參數。
不允許使用 C 語言鏈接。

除了上述限制之外,字面量運算符(和字面量運算符模板)是普通函數(和函數模板),它們可以聲明為 inlineconstexpr ,它們可能具有內部或外部鏈接,它們可以顯式調用,它們的地址可以被獲取等。

注意

由於最大吞噬規則,以 eE (C++17 起還有 pP) 結束的用戶定義整數和浮點字面量,在后隨運算符 +- 時,必須在源碼中以空白符或括號與運算符分隔:

long double operator""_E(long double);
long double operator""_a(long double);
int operator""_p(unsigned long long);
 
auto x = 1.0_E+2.0;   // 錯誤
auto y = 1.0_a+2.0;   // OK
auto z = 1.0_E +2.0;  // OK
auto q = (1.0_E)+2.0; // OK
auto w = 1_p+2;       // 錯誤
auto u = 1_p +2;      // OK

同樣的規則適用於后隨整數或浮點用戶定義字面量的點運算符:

#include <chrono>
using namespace std::literals;
auto a = 4s.count();   // 錯誤
auto b = 4s .count();  // OK
auto c = (4s).count(); // OK

否則會組成單個非法預處理數字記號(例如 1.0_E+2.0 或 4s.count),這導致編譯失敗。

最大吞噬規則

最大吞噬規則指的是編譯器在處理源文件時,翻譯階段中解析預處理記號的一種特性。
如果一個給定字符前的輸入已被解析為預處理記號,下一個預處理記號通常會由能構成預處理記號的最長字符序列構成,即使這樣處理會導致后續分析失敗。這常被稱為最大吞噬。
比如經典的 i+++++i 會被解析為 i++ ++ +i 而報錯,而不是人們所想的 i++ + ++i

示例

#include <iostream>
using std::cout;
namespace test
{
	struct My_type
	{
		double value;
		void print() const { cout << "print value: " << value << '\n'; }
	};
	namespace literal
	{
		  // 輸出 整型 和 浮點型
		void operator"" _output(const char* num)
		{
			cout << "output: " << num << '\n';
		}
		  // 類型轉換
		inline My_type operator"" _to_My_type(unsigned long long num)
		{
			My_type tmp;
			tmp.value = num;
			return tmp;
		}
		  // 四舍五入
		constexpr int operator"" _round(long double num)
		{
			return int( num + 0.5 );
		}
		  // 獲取字符的 ASCII 碼
		constexpr int operator"" _get_ascii(char c)
		{
			return int(c);
		}
		  // 獲取字符串長度
		constexpr size_t operator"" _get_len(const char* s, size_t len)
		{
			return len;
		}
	}
}
  // 一般來說,字面量容易重名,所以常常放在命名空間里
using namespace test::literal;
int main()
{
	114514_output;
	3.14_output;
	2022_to_My_type .print();  // 注意這里的空格
	cout <<  "round: " <<   9.86_round;
	cout <<   "\n\t"   <<   9.39_round   << '\n';
	cout <<  "ascii: " << 'A'_get_ascii  << '\n';
	cout << "strlen: " << "abcd"_get_len << '\n';
	return 0;
}
輸出結果
output: 114514
output: 3.14
print value: 2022
round: 10
        9
ascii: 65
strlen: 4

C++ 標准庫對用戶定義字面量的應用

此處僅舉部分例子。

字面量運算符 類型 作用 起始版本 備注
operator""if std::complex字面量 表示純虛數 C++14 定義於內聯命名空間std::literals::complex_literals
operator""i
operator""il
operator""h std::chrono::duration字面量 表示小時 C++14 定義於內聯命名空間std::literals::chrono_literals
operator""min 表示分鍾
operator""s 表示秒
operator""ms 表示毫秒
operator""us 表示微秒
operator""ns 表示納秒
operator""y std::chrono::year字面量 表示特定年 C++20
operator""d std::chrono::day字面量 表示月內日期
operator""s std::basic_string字面量 轉換字符數組字面量為std::basic_string C++14 定義於內聯命名空間std::literals::string_literals
operator""sv std::basic_string_view字面量 創建一個字符數組字面量的字符串視圖 C++17 定義於內聯命名空間std::literals::string_view_literals

顯然 std 沒有遵守以下划線開頭的命名規范


參考:
1.https://en.cppreference.com/w/cpp/language/user_literal
2.https://en.cppreference.com/w/cpp/language/translation_phases#.E6.9C.80.E5.A4.A7.E5.90.9E.E5.99.AC


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM