MikyMax
MikyMax

Reputation: 11

Optimise C++ const std::string literal declaration for compile time initialization and runtime performance

I wish to optimise the use of const string literals in a bulky c++ 20 project to enhance compilation and runtime performance In lots of source files string literals are declared with const std::string as in the example below (tens of string literals are defined then concatenated to form new ones):

Before

// Example
namespace {
const std::string kPluginApi("plugins-api");
const std::string kGlobal(".global");
const std::string kInternal(".internal");
}

namespace {
const std::string kGLobalApiPlugin = kPluginApi + kGlobal;
const std::string kInternalApiPlugin = kPluginApi + kInternal;
}

The problem is that std::string are usually initialized at runtime despite the const qualifier.

Since std::string typically involve dynamic memory allocation, hence runtime initialisation, I thought of using constexpr const char * to ensure compile time initialisation when possible as seen below :

After

namespace {
constexpr const char * kPluginApi = "plugins-api";
constexpr const char * kGlobal    = ".global";
constexpr const char * kInternal  = ".internal";
}

namespace {
const std::string kGLobalApiPlugin = std::string(kPluginApi) + kGlobal;
const std::string kInternalApiPlugin = std::string(kPluginApi) + kInternal;
}

Upvotes: 1

Views: 108

Answers (1)

Pepijn Kramer
Pepijn Kramer

Reputation: 13076

You can use compile time concatenation like this :

#include <array>
#include <string_view>

namespace ce
{
template <typename input_iterator_t, typename output_iterator_t>
constexpr void ce_copy(input_iterator_t first, input_iterator_t last, output_iterator_t to)
{
    for(auto it = first; it != last; ++it, ++to)
    {
        *to = *it;
    }
}

template <std::size_t N>
class string final
{
public:
    constexpr string() = default;

    constexpr string(const char (&str)[N]) noexcept
    {
        ce_copy(str, str + N, m_str.begin());
    }

    constexpr string(const string&) = default;

    //explicit conversion to string_view
    constexpr std::string_view sv() const noexcept
    {
        return std::string_view{ c_str(), N - 1 };
    }

    // implicit conversion to string_view
    constexpr operator std::string_view() const noexcept
    {
        return std::string_view{ c_str(), N - 1 };
    }

    constexpr const char* c_str() const noexcept
    {
        return m_str.data();
    }

    constexpr char* str() noexcept
    {
        return m_str.data();
    }

    constexpr auto cbegin() const noexcept
    {
        return m_str.cbegin();
    }

    constexpr auto cend() const noexcept
    {
        return m_str.cend();
    }

    constexpr bool operator==(const char* rhs) const noexcept
    {
        return sv() == std::string_view{ rhs };
    }

    template <std::size_t M>
    constexpr auto concat(const string<M>& rhs) const noexcept
    {
        string<N + M - 1> result;
        ce_copy(cbegin(), cend(), result.str());
        ce_copy(rhs.cbegin(), rhs.cend(), result.str() + N - 1);
        return result;
    }

    constexpr auto operator[](const std::size_t index) const noexcept
    {
        return m_str[index];
    }

    constexpr auto& operator[](const std::size_t index) noexcept
    {
        return m_str[index];
    }

private:
    std::array<char, N> m_str{};
};

} //namespace ce

static constexpr ce::string kGlobal{ ".global" };
static constexpr ce::string kInternal(".internal");
static constexpr ce::string kPluginApi("plugins-api");

static constexpr ce::string kGLobalApiPlugin = kPluginApi.concat(kGlobal);
static constexpr ce::string kLocalApiPlugin = kPluginApi.concat(kInternal);

int main()
{
    static_assert(kGLobalApiPlugin == "plugins-api.global");
}

Upvotes: 0

Related Questions