我有很大的代码库,该代码库__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__);
Run Code Online (Sandbox Code Playgroud)
效果很好,我检查了汇编代码,在编译时对行进行了修整,只有文件名以二进制形式出现。
但是,此表示法太冗长。我想为此使用宏,但失败了。上面链接中的建议示例
#define __SHORT_FILE__ ({constexpr cstr sf__ {past_last_slash(__FILE__)}; sf__;})
Run Code Online (Sandbox Code Playgroud)
不适用于MSVC编译器(我正在使用MSVC 2017)。使用c ++ 17还有其他方法吗?
UPD1:按功能https://godbolt.org/z/tAU4j7修剪的lang
UPD2:看起来可以使用函数在编译时进行修整,但是完整的字符串将以二进制形式出现。
您可以使用std::string_view:
constexpr auto filename(std::string_view path)
{
return path.substr(path.find_last_of('/') + 1);
}
Run Code Online (Sandbox Code Playgroud)
用法:
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");
Run Code Online (Sandbox Code Playgroud)
看到它编译(godbolt.org)。
对于Windows:
constexpr auto filename(std::wstring_view path)
{
return path.substr(path.find_last_of(L'\\') + 1);
}
Run Code Online (Sandbox Code Playgroud)
这个想法是创建字符的截断数组,但是它只需要使用编译时功能。通过带有可变字符包的可变参数模板生成数据数组会强制编译器生成与传递的字符串文字没有直接关系的数据。这样,编译器无法使用输入字符串文字,尤其是当此字符串很长时。
带有叮当声的Godbolt:https://godbolt.org/z/WdKNjB 。
带有msvc的Godbolt:https://godbolt.org/z/auMEIH 。
唯一的问题是模板深度编译器设置。
首先,我们定义int可变参数模板以存储索引序列:
template <int... I>
struct Seq {};
Run Code Online (Sandbox Code Playgroud)
将int推送至Seq:
template <int V, typename T>
struct Push;
template <int V, int... I>
struct Push<V, Seq<I...>>
{
using type = Seq<V, I...>;
};
Run Code Online (Sandbox Code Playgroud)
创建序列:
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>> {};
Run Code Online (Sandbox Code Playgroud)
现在我们可以使编译时间为int,即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[];
Run Code Online (Sandbox Code Playgroud)
接下来,我们将选择的字符提取为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;
};
Run Code Online (Sandbox Code Playgroud)
WRAPPER 是包含我们的字符串文字的类型。
快完成了 缺少的部分是找到最后一个斜杠。我们可以使用问题中找到的代码的修改后的版本,但这一次它返回偏移量而不是指针:
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);
}
Run Code Online (Sandbox Code Playgroud)
获取字符串大小的最后一个工具:
constexpr int StrLen(const char * str) {
if (*str == '\0') return 0;
return StrLen(str + 1) + 1;
}
Run Code Online (Sandbox Code Playgroud)
使用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; \
}()
Run Code Online (Sandbox Code Playgroud)
Lambda函数在使用此宏时具有很好的价值感。它还为定义Wrapper结构创建了范围。使用宏使用插入的字符串文字生成此结构,会导致字符串文字受限于类型的情况。
老实说,我不会在生产中使用这种代码。它正在杀死编译器。
出于安全性和内存使用的考虑,我都建议使用带有自定义短路径的docker进行构建。