C ++编译时子字符串 [英] C++ compile-time substring

查看:64
本文介绍了C ++编译时子字符串的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的代码库很大,它广泛使用 __ FILE __ 进行日志记录.但是,它包含完整路径,不需要(1),(2)可能会违反安全性.

I have very big code-base, which uses __FILE__ extensively for logging. However, it includes full path, which is (1) not needed, (2) might case security violations.

我正在尝试编写编译时子字符串表达式.最终以此解决方案

I'm trying to write compile-time sub-string expression. Ended up with this solution

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__);

这很好,我已经检查了汇编代码,在编译时对行进行了修剪,只有文件名以二进制形式出现.

This works good, I've checked assembly code, line is trimmed in compile time, only file name is present in binary.

但是,此表示法过于冗长.我想为此使用宏,但失败了.上面链接中的建议示例

However, this notation is too verbose. I would like to use macro for this, but failed. Proposed example from the link above

#define __SHORT_FILE__ ({constexpr cstr sf__ {past_last_slash(__FILE__)}; sf__;})

不适用于MSVC编译器(我正在使用MSVC 2017).使用c ++ 17还有其他方法吗?

doesn't work for MSVC compiler (I'm using MSVC 2017). Is there any other method do to so using c++17?

UPD1:按功能rel ="noreferrer"> https://godbolt.org/z/tAU4j7

UPD2:看起来可以使用函数在编译时进行修整,但是完整的字符串将以二进制形式出现.

UPD2: looks like it's possible to do trim on compile time using functions, but full string is swill be present in binary.

推荐答案

该想法是创建截断的字符数组,但只需要使用编译时功能.通过带有可变字符包的可变参数模板生成数据数组会强制编译器生成与传递的字符串文字没有直接关系的数据.这样,编译器将无法使用输入字符串文字,尤其是当此字符串很长时.

The idea is to create truncated array of characters, but it needs to use only compile time features. Generating data array through variadic template with pack of char forces compiler to generate data without direct relation to passed string literal. This way compiler cannot use input string literal, especially when this string is long.

带有叮当声的Godbolt: https://godbolt.org/z/WdKNjB .

Godbolt with clang: https://godbolt.org/z/WdKNjB.

使用msvc进行的Godbolt: https://godbolt.org/z/auMEIH .

Godbolt with msvc: https://godbolt.org/z/auMEIH.

唯一的问题是模板深度编译器设置.

The only problem is with template depth compiler settings.

首先,我们定义int可变参数模板以存储索引序列:

First we define int variadic template to store sequence of indexes:

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> .仍然需要一些东西来将选定的字符存储在数组中,但是要使用编译时间表示,这是带有字符的可变参数模板参数:

Now we can make sequence of compile time ints, meaning that MakeSeq<3,7> == Seq<3,4,5,6,7>. Still we need something to store selected characters in array, but using compile time representation, which is variadic template parameter with characters:

template<char... CHARS>
struct Chars {
    static constexpr const char value[] = {CHARS...};
};
template<char... CHARS>
constexpr const char Chars<CHARS...>::value[];

接下来,我们将提取的字符提取为 Chars 类型:

Next we something to extract selected characters into Chars type:

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 是一种包含我们字符串文字的类型.

WRAPPER is a type that contain our string literal.

快完成了.缺少的部分是找到最后一个斜杠.我们可以使用问题中找到的代码的修改后的版本,但是这次它返回偏移量而不是指针:

Almost done. The missing part is to find last slash. We can use modified version of the code found in the question, but this time it returns offset instead of pointer:

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 to get string size:

constexpr int StrLen(const char * str) {
    if (*str == '\0') return 0;
    return StrLen(str + 1) + 1;
}

使用define将所有内容组合在一起:

Combining everything together using 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 结构创建了一个范围.使用宏通过插入的字符串文字生成此结构,会导致字符串文字绑定到类型的情况.

Lambda function is to have nice, value-like feeling when using this macro. It also creates a scope for defining Wrapper structure. Generating this structure with inserted string literal using macro, leads to situation when the string literal is bounded to type.

老实说,我不会在生产中使用这种代码.它正在杀死编译器.

Honestly I would not use this kind of code in production. It is killing compilers.

出于安全性和内存使用的考虑,我都建议使用带有自定义短路径的Docker.

Both, in case of security reasons and memory usage, I would recommend using docker with custom, short paths for building.

这篇关于C ++编译时子字符串的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆