新聞中心
背景
std::format
在傳參數(shù)量少于格式串所需參數(shù)數(shù)量時(shí),會(huì)拋出異常。而在大部分的應(yīng)用場景下,參數(shù)數(shù)量不一致提供編譯報(bào)錯(cuò)更加合適,可以促進(jìn)我們更早發(fā)現(xiàn)問題并進(jìn)行改正。
創(chuàng)新互聯(lián)主要從事做網(wǎng)站、成都做網(wǎng)站、網(wǎng)頁設(shè)計(jì)、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)景泰,十年網(wǎng)站建設(shè)經(jīng)驗(yàn),價(jià)格優(yōu)惠、服務(wù)專業(yè),歡迎來電咨詢建站服務(wù):028-86922220
最終效果
// 測試輸出接口。
template
void Print(const std::string& _Fmt, const T&... _Args)
{
cout << std::vformat(_Fmt, std::make_format_args(_Args...)) << endl;
}
// 封裝宏,實(shí)現(xiàn)參數(shù)數(shù)量一致的檢查
#define PRINT(fmt, ...) \
do { static_assert(GetFormatStringArgsNum(fmt) == decltype(VariableArgsNumHelper(__VA_ARGS__))::value, "Invalid format string or mismatched number of arguments"); Print(fmt, __VA_ARGS__); } while(0)
int main()
{
PRINT("{}", "hello");
PRINT("{} {}", "hello");
return 0;
}
上例代碼中,使用PRINT
宏封裝了Print
函數(shù),后續(xù)使用PRINT
進(jìn)行控制臺(tái)輸出,如果出現(xiàn)參數(shù)數(shù)量不一致,將產(chǎn)生編譯報(bào)錯(cuò):Invalid format string or mismatched number of arguments
。
所用技術(shù)
-
靜態(tài)斷言:
static_assert
-
格式串參數(shù)數(shù)量獲?。?/strong>
GetFormatStringArgsNum
,該接口聲明為constexpr
,從而獲得編譯期執(zhí)行的能力。其實(shí)現(xiàn)大致為遍歷字符串,檢查其中{}
的數(shù)量。 -
傳參數(shù)量的獲?。?/strong> 由于使用宏進(jìn)行封裝,最后其實(shí)就是需要獲得
__VA_ARGS__
中附帶了幾個(gè)參數(shù),網(wǎng)上可以搜到各種解決方案,這里采用的是聲明一個(gè)模板函數(shù),模板函數(shù)返回integral_constant
結(jié)構(gòu)體,其對(duì)不同的參數(shù)數(shù)量,自動(dòng)生成不同的結(jié)構(gòu)體類型,之后使用decltype(VariableArgsNumHelper(__VA_ARGS__))
獲得返回值類型,并從返回值類型中獲得代表參數(shù)數(shù)量的常量值,由于運(yùn)行期用不到該函數(shù),因此只提供聲明,不提供實(shí)現(xiàn)。
整體代碼
#include
#include
#include
using namespace std;
constexpr int GetFormatStringArgsNum(const std::string& fmt)
{
enum STATE
{
NORMAL, // 正在解析普通串
REPLACEMENT, // 正在解析大括號(hào)中的內(nèi)容
};
// 按標(biāo)準(zhǔn)規(guī)定,格式串中要么都指定參數(shù)編號(hào),要么都不指定
// 原文:
// The arg-ids in a format string must all be present or all be omitted.
// Mixing manual and automatic indexing is an error.
enum RULE
{
UNKNOWN, // 格式串規(guī)則
SPECIFIEDID, // 指定編號(hào),如{0}
UNSPECIFIEDID, // 不指定編號(hào),如{}
};
// 指定參數(shù)編號(hào)的最大值
const int MAX_ARGS_NUM = ;
// 初始狀態(tài)
STATE state = NORMAL;
// 初始規(guī)則
RULE rule = UNKNOWN;
// 當(dāng)前參數(shù)編號(hào)
int nIndex = -1;
// 參數(shù)數(shù)量
int nArgsNum = 0;
for (int i = 0; i < fmt.size(); ++i)
{
switch (state)
{
case NORMAL:
{
// 普通串解析時(shí),遇到左大括號(hào)或右大括號(hào),才有可能改變狀態(tài)
if (fmt[i] == '{')
{
if (i + 1 < fmt.size() && fmt[i + 1] == '{')
{
// 遇到 {{,則將他們視為普通字符
++i;
}
else
{
// 進(jìn)入替換串狀態(tài)
state = REPLACEMENT;
}
}
else if (fmt[i] == '}')
{
++i;
if (i >= fmt.size() || fmt[i] != '}')
{
// 普通串解析狀態(tài),遇上右大括號(hào)時(shí),只有當(dāng)接下來也是右大括號(hào)時(shí),才屬于合法串
return -1;
}
}
}
break;
case REPLACEMENT:
{
// 替換串狀態(tài)下,正常只會(huì)遇到右大括號(hào)、數(shù)字、冒號(hào),其他符號(hào)均為錯(cuò)誤
if (fmt[i] == '}')
{
// 遇到右大括號(hào),則進(jìn)入普通串解析狀態(tài),這里不考慮}},正常{} 中間不應(yīng)該出現(xiàn)}
state = NORMAL;
// 如果之前某個(gè){} 已經(jīng)指定參數(shù)編號(hào),則所有參數(shù)都應(yīng)該指定編號(hào)
if (rule == SPECIFIEDID)
{
// 如果這個(gè){} 不指定編號(hào),則視為非法格式串
if (nIndex == -1)
{
return -1;
}
// 在指定編號(hào)的情況下,可變參數(shù)的數(shù)量至少要比編號(hào)大1
nArgsNum = std::max(nArgsNum, nIndex + 1);
// 重置當(dāng)前編號(hào)
nIndex = -1;
}
else
{
// 如果當(dāng)前規(guī)則未明或者當(dāng)前規(guī)則為不指定編號(hào),則參數(shù)數(shù)量進(jìn)行自增。
state = NORMAL;
rule = UNSPECIFIEDID;
++nArgsNum;
}
}
else if (fmt[i] >= '0' && fmt[i] <= '9')
{
// 遇到數(shù)字,說明指定了參數(shù)編號(hào)
if (rule == UNSPECIFIEDID)
{
// 如果當(dāng)前規(guī)則已明確為不指定編號(hào),則視為非法格式串
return -1;
}
else
{
// 否則,將當(dāng)前規(guī)則改為指定編號(hào),并維護(hù)當(dāng)前編號(hào)
rule = SPECIFIEDID;
if (nIndex == -1)
{
nIndex = 0;
}
nIndex = nIndex * 10 + (fmt[i] - '0');
if (nIndex >= MAX_ARGS_NUM)
{
// 當(dāng)前編號(hào)大于最大上限,則直接視為非法格式串
return -1;
}
}
}
else if (fmt[i] == ':')
{
// 遇到冒號(hào),說明接下來是格式串規(guī)則,直接跳過
for (; i + 1 < fmt.size() && fmt[i + 1] != '}'; ++i)
{
;
}
}
else
{
// 解析替換串時(shí),遇上其他字符,均將格式串視為非法。
return -1;
}
}
break;
}
}
// 最終狀態(tài)必須為普通串解析狀態(tài)。
return state == NORMAL ? nArgsNum : -1;
}
// 可變參數(shù)數(shù)量輔助器
template
std::integral_constant VariableArgsNumHelper(const Args & ...);
// 測試輸出接口。
template
void Print(const std::string& _Fmt, const T&... _Args)
{
cout << std::vformat(_Fmt, std::make_format_args(_Args...)) << endl;
}
// 封裝宏,實(shí)現(xiàn)參數(shù)數(shù)量一致的檢查
#define PRINT(fmt, ...) \
do { static_assert(GetFormatStringArgsNum(fmt) == decltype(VariableArgsNumHelper(__VA_ARGS__))::value, "Invalid format string or mismatched number of arguments"); Print(fmt, __VA_ARGS__); } while(0)
int main()
{
PRINT("{} {}", "hello");
return 0;
}
名稱欄目:通過宏封裝實(shí)現(xiàn)std::format編譯期檢查參數(shù)數(shù)量是否一致
文章鏈接:http://fisionsoft.com.cn/article/dsoigds.html