Задать вопрос
@Mercury13
Программист на «си с крестами» и не только

Как в шаблоне Си++ отличить константный буфер от строкового литерала?

Ради оптимизации на обычных строковых литералах (первый юнит-тест) я пошёл на неочевидную работу на константных буферах (второй юнит-тест). Для краткости версии для wchar_t опущены. Система тестирования, если что, Google Test. Строки бывают страшные, иногда дважды заэкранированные (сначала в JS, потом в Си), и даже добавлять к ним «sv» боязно.

Собственно, вопрос: как в шаблоне отличить строковый литерал от константного буфера, чтобы применить к ним разные len и append?
namespace str {
    namespace detail {
        inline constexpr size_t len(char) { return 1; }
        inline size_t len(const char* s) { return strlen(s); }
        inline size_t len(const std::string& s) { return s.length(); }
        inline constexpr size_t len(std::string_view s) { return s.length(); }
        template <size_t N> inline constexpr size_t arrLen(const char (&)[N])
            { if constexpr (N == 0) return 0; else return N - 1; }
        template <size_t N> inline constexpr size_t arrLen(char* (&s)[N]) { return strnlen(s, N); }
    }

    template <class T>
    constexpr inline size_t len(T&& s) {
        if constexpr (std::is_array_v<std::remove_reference_t<T>>) {
            return detail::arrLen(s);
        } else {
            return detail::len(s);
        }
    }

    // append - 0 strings
    inline void append(std::string&) {}

    // append - 1 string
    namespace detail {
        inline void append1(std::string& r, char s) { r.push_back(s); }
        void append1(std::string&, wchar_t) = delete;
        inline void append1(std::string& r, const char* s) { r.append(s); }
        inline void append1(std::string& r, const std::string& s) { r.append(s); }
        inline void append1(std::string& r, std::string_view s) { r.append(s); }
        template <size_t N> inline void arrAppend(std::string& r, const char (&s)[N])
            { if constexpr (N > 0) r.append(s, N - 1); }
        template <size_t N> inline void arrAppend(std::string& r, char (&s)[N])
            { r.append(s, strnlen(s, N)); }
        template <class R, class T>
        inline void append2(R&& r, T&& s) {
            if constexpr (std::is_array_v<std::remove_reference_t<T>>) {
                arrAppend(r, s);
            } else {
                append1(r, s);
            }
        }
    }

    template <class T>
    inline void append (std::string& r, T&& s) { detail::append2(r, s); }

    // append - 2+ strings
    template <class T, class... Args>
    inline void append(std::string& r, T&& s, Args&& ... args) {
        r.reserve(r.length() + len(s) + (len(args) + ...));
        detail::append2(r, std::forward<T>(s));
        (detail::append2(r, std::forward<Args>(args)), ...);
    }

    template <class T>
    struct SCharType;

    template<> struct SCharType<std::string>        { using C = char;    };
    template<> struct SCharType<char>               { using C = char;    };
    template<> struct SCharType<char*>              { using C = char;    };
    template<size_t N> struct SCharType<char[N]>    { using C = char;    };
    template<> struct SCharType<const char*>        { using C = char;    };
    template<> struct SCharType<std::string_view>   { using C = char;    };

    template <class T>
    using CharType = typename SCharType<std::remove_cv_t<
                                        std::remove_reference_t<T>>>::C;

    template <class T>
    using StringType = std::basic_string<CharType<T>>;

    namespace detail {
        template <class T, class U>
        constexpr void checkForSame(T&&, U&&) {
            static_assert (std::is_same_v<CharType<T>, CharType<U>>,
                "Should have identical char types");
        }
    }   // ns detail

    ///
    /// @warning
    ///   For semi-filled non-const buffer does strnlen.
    ///   For const buffer adds the entire data except the last byte
    ///     (very rare situation in real life)
    ///
    template <class T, class...Args>
    [[nodiscard]] inline auto concat(T&& s, Args&& ... args) {
        (detail::checkForSame(std::forward<T>(s), std::forward<Args>(args)), ...);
        StringType<T> r;
        append(r, std::forward<T>(s), std::forward<Args>(args)...);
        return r;
    }
}

TEST (Concat, AsciiZerosLiteral)
{
    auto r = str::concat("alpha\0\0", "bravo"sv);
    EXPECT_EQ("alpha\0\0bravo"sv, r);
}

TEST (Concat, AsciiZeros1)
{
    char a[10];
    std::fill(std::begin(a), std::end(a), 0);
    strcpy(a, "alpha");
    std::string_view b = "bravo";

    auto r = str::concat(const_cast<const decltype (a)&>(a), b);
    EXPECT_EQ("alpha\0\0\0\0bravo"sv, r);
}
  • Вопрос задан
  • 118 просмотров
Подписаться 1 Средний 3 комментария
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы