簡體   English   中英

C++ 編譯時 substring

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM