[英]C++ compile-time substring
我有非常大的代碼庫,廣泛使用__FILE__
進行日志記錄。 但是,它包含完整路徑,(1) 不需要,(2) 可能會違反安全規定。
我正在嘗試編寫編譯時子字符串表達式。 結束了這個解決方案
static constexpr cstr PastLastSlash(cstr str, cstr last_slash)
{
return *str == '\0' ? last_slash : *str == '/' ? PastLastSlash(str + 1, str + 1) : PastLastSlash(str + 1, last_slash);
}
static constexpr cstr PastLastSlash(cstr str)
{
return PastLastSlash(str, str);
}
// usage
PastLastSlash(__FILE__);
這很好用,我檢查了匯編代碼,在編譯時修剪了行,只有文件名以二進制形式存在。
但是,這種表示法過於冗長。 我想為此使用宏,但失敗了。 上面鏈接中的建議示例
#define __SHORT_FILE__ ({constexpr cstr sf__ {past_last_slash(__FILE__)}; sf__;})
不適用於 MSVC 編譯器(我使用的是 MSVC 2017)。 使用c++17還有其他方法嗎?
UPD1: clang 被 function https 修剪過://godbolt.org/z/tAU4j7
UPD2:看起來可以使用函數在編譯時進行修剪,但完整的字符串將以二進制形式存在。
這個想法是創建截斷的字符數組,但它只需要使用編譯時功能。 通過可變參數模板生成數據數組,使用一組char強制編譯器生成數據,而不與傳遞的字符串文字直接相關。 這種方式編譯器不能使用輸入字符串文字,特別是當此字符串很長時。
Godbolt with clang: https ://godbolt.org/z/WdKNjB。
帶有msvc的Godbolt: https ://godbolt.org/z/auMEIH。
唯一的問題是模板深度編譯器設置。
首先,我們定義int variadic模板來存儲索引序列:
template <int... I>
struct Seq {};
將int推送到Seq
:
template <int V, typename T>
struct Push;
template <int V, int... I>
struct Push<V, Seq<I...>>
{
using type = Seq<V, I...>;
};
創建序列:
template <int From, int To>
struct MakeSeqImpl;
template <int To>
struct MakeSeqImpl<To, To>
{
using type = Seq<To>;
};
template <int From, int To>
using MakeSeq = typename MakeSeqImpl<From, To>::type;
template <int From, int To>
struct MakeSeqImpl : Push<From, MakeSeq<From + 1, To>> {};
現在我們可以編寫一個編譯時間序列,這意味着MakeSeq<3,7> == Seq<3,4,5,6,7>
。 我們還需要一些東西來存儲數組中的選定字符,但是使用編譯時表示,這是帶有字符的可變參數模板參數:
template<char... CHARS>
struct Chars {
static constexpr const char value[] = {CHARS...};
};
template<char... CHARS>
constexpr const char Chars<CHARS...>::value[];
接下來我們將所選字符提取到Chars
類型:
template<typename WRAPPER, typename IDXS>
struct LiteralToVariadicCharsImpl;
template<typename WRAPPER, int... IDXS>
struct LiteralToVariadicCharsImpl<WRAPPER, Seq<IDXS...> > {
using type = Chars<WRAPPER::get()[IDXS]...>;
};
template<typename WRAPPER, typename SEQ>
struct LiteralToVariadicChars {
using type = typename LiteralToVariadicCharsImpl<WRAPPER, SEQ> :: type;
};
WRAPPER
是一個包含字符串文字的類型。
幾乎完成了。 缺少的部分是找到最后一個斜線。 我們可以使用問題中找到的代碼的修改版本,但這次它返回offset而不是指針:
static constexpr int PastLastOffset(int last_offset, int cur, const char * const str)
{
if (*str == '\0') return last_offset;
if (*str == '/') return PastLastOffset(cur + 1, cur + 1, str + 1);
return PastLastOffset(last_offset, cur + 1, str + 1);
}
last util獲取字符串大小:
constexpr int StrLen(const char * str) {
if (*str == '\0') return 0;
return StrLen(str + 1) + 1;
}
使用define將所有內容組合在一起:
#define COMPILE_TIME_PAST_LAST_SLASH(STR) \
[](){ \
struct Wrapper { \
constexpr static const char * get() { return STR; } \
}; \
using Seq = MakeSeq<PastLastOffset(0, 0, Wrapper::get()), StrLen(Wrapper::get())>; \
return LiteralToVariadicChars<Wrapper, Seq>::type::value; \
}()
Lambda函數在使用這個宏時具有很好的,有價值的感覺。 它還創建了一個定義Wrapper
結構的范圍。 使用宏生成此結構並使用插入的字符串文字,導致字符串文字限制為類型時的情況。
老實說,我不會在生產中使用這種代碼。 它正在殺死編譯器。
如果出於安全原因和內存使用情況,我建議使用docker和自定義的短路徑進行構建。
你可以使用std::string_view
:
constexpr auto filename(std::string_view path)
{
return path.substr(path.find_last_of('/') + 1);
}
用法:
static_assert(filename("/home/user/src/project/src/file.cpp") == "file.cpp");
static_assert(filename("./file.cpp") == "file.cpp");
static_assert(filename("file.cpp") == "file.cpp");
看到它編譯 (godbolt.org)。
對於Windows:
constexpr auto filename(std::wstring_view path)
{
return path.substr(path.find_last_of(L'\\') + 1);
}
使用 C++17,您可以執行以下操作 ( https://godbolt.org/z/68PKcsPzs ):
#include <cstdio>
#include <array>
namespace details {
template <const char *S, size_t Start = 0, char... C>
struct PastLastSlash {
constexpr auto operator()() {
if constexpr (S[Start] == '\0') {
return std::array{C..., '\0'};
} else if constexpr (S[Start] == '/') {
return PastLastSlash<S, Start + 1>()();
} else {
return PastLastSlash<S, Start + 1, C..., (S)[Start]>()();
}
}
};
}
template <const char *S>
struct PastLastSlash {
static constexpr auto a = details::PastLastSlash<S>()();
static constexpr const char * value{a.data()};
};
int main() {
static constexpr char f[] = __FILE__;
puts(PastLastSlash<f>::value);
return 0;
}
對於 C++14,由於 constexpr ( https://godbolt.org/z/bzGec5GMv ) 的限制,它有點復雜:
#include <cstdio>
#include <array>
namespace details {
// Generic form: just add the character to the list
template <const char *S, char ch, size_t Start, char... C>
struct PastLastSlash {
constexpr auto operator()() {
return PastLastSlash<S, S[Start], Start + 1, C..., ch>()();
}
};
// Found a '/', reset the character list
template <const char *S, size_t Start, char... C>
struct PastLastSlash<S, '/', Start, C...> {
constexpr auto operator()() {
return PastLastSlash<S, S[Start], Start + 1>()();
}
};
// Found the null-terminator, ends the search
template <const char *S, size_t Start, char... C>
struct PastLastSlash<S, '\0', Start, C...> {
constexpr auto operator()() {
return std::array<char, sizeof...(C)+1>{C..., '\0'};
}
};
}
template <const char *S>
struct PastLastSlash {
const char * operator()() {
static auto a = details::PastLastSlash<S, S[0], 0>()();
return a.data();
}
};
static constexpr char f[] = __FILE__;
int main() {
puts(PastLastSlash<f>{}());
return 0;
}
使用 C++20,應該可以將__FILE__
直接傳遞給模板,而不需要那些static constexpr
變量
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.