Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create a constexpr C string from concatenation of a some string literal and an int template parameter

I have a class with an int template parameter. Under some circumstances I want it to output an error message. This message should be a concatenated string from some fixed text and the template parameters. For performance reasons I'd like to avoid building up this string at runtime each time the error occurs and theoretically both, the string literal and the template parameter are known at compiletime. So I'm looking for a possibility to declare it as a constexpr.

Code example:

template<int size>
class MyClass
{
    void onError()
    {
        // obviously won't work but expressing the concatenation like
        // it would be done with a std::string for clarification 
        constexpr char errMsg[] = "Error in MyClass of size " + std::to_string (size) + ": Detailed error description\n";

        outputErrorMessage (errMsg);
    }
}
like image 589
PluginPenguin Avatar asked Nov 07 '18 12:11

PluginPenguin


3 Answers

Using static const would allow to compute it only once (but at runtime):

template<int size>
class MyClass
{
    void onError()
    {
        static const std::string = "Error in MyClass of size "
                                  + std::to_string(size)
                                  + ": Detailed error description\n";

        outputErrorMessage(errMsg);
    }
};

If you really want to have that string at compile time, you might use std::array, something like:

template <std::size_t N>
constexpr std::size_t count_digit() {
    if (N == 0) {
        return 1;   
    }
    std::size_t res = 0;
    for (int i = N; i; i /= 10) {
        ++res;
    }
    return res;
}

template <std::size_t N>
constexpr auto to_char_array()
{
    constexpr auto digit_count = count_digit<N>();
    std::array<char, digit_count> res{};

    auto n = N;
    for (std::size_t i = 0; i != digit_count; ++i) {
        res[digit_count - 1 - i] = static_cast<char>('0' + n % 10);
        n /= 10;
    }
    return res;
}

template <std::size_t N>
constexpr std::array<char, N - 1> to_array(const char (&a)[N])
{
    std::array<char, N - 1> res{};

    for (std::size_t i = 0; i != N - 1; ++i) {
        res[i] = a[i];
    }
    return res;
}

template <std::size_t ...Ns>
constexpr std::array<char, (Ns + ...)> concat(const std::array<char, Ns>&... as)
{
    std::array<char, (Ns + ...)> res{};
    std::size_t i = 0;
    auto l = [&](const auto& a) { for (auto c : a) {res[i++] = c;} };

    (l(as), ...);
    return res;
}

And finally:

template<int size>
class MyClass
{
public:
    void onError()
    {
        constexpr auto errMsg = concat(to_array("Error in MyClass of size "),
                                  to_char_array<size>(),
                                  to_array(": Detailed error description\n"),
                                  std::array<char, 1>{{0}});

        std::cout << errMsg.data();
    }
};

Demo

like image 66
Jarod42 Avatar answered Nov 07 '22 04:11

Jarod42


Here's my solution. Tested on godbolt:

#include <string_view>
#include <array>
#include <algorithm>

void outputErrorMessage(std::string_view s);

template<int N> struct cint
{
    constexpr int value() const { return N; }
};

struct concat_op {};

template<std::size_t N>
struct fixed_string
{
    constexpr static std::size_t length() { return N; }
    constexpr static std::size_t capacity() { return N + 1; }

    template<std::size_t L, std::size_t R>
    constexpr fixed_string(concat_op, fixed_string<L> l, fixed_string<R> r)
    : fixed_string()
    {
        static_assert(L + R == N);   
        overwrite(0, l.data(), L);
        overwrite(L, r.data(), R);     
    }

    constexpr fixed_string()
    : buffer_ { 0 }
    {
    }

    constexpr fixed_string(const char (&source)[N + 1])
    : fixed_string()
    {
        do_copy(source, buffer_.data());
    }

    static constexpr void do_copy(const char (&source)[N + 1], char* dest)
    {
        for(std::size_t i = 0 ; i < capacity() ; ++i)
            dest[i] = source[i];
    }

    constexpr const char* data() const
    {
        return buffer_.data();
    }

    constexpr const char* data()
    {
        return buffer_.data();
    }

    constexpr void overwrite(std::size_t where, const char* source, std::size_t len)
    {
        auto dest = buffer_.data() + where;
        while(len--)
            *dest++ = *source++;
    }

    operator std::string_view() const
    {
        return { buffer_.data(), N };
    }

    std::array<char, capacity()> buffer_;
};

template<std::size_t N> fixed_string(const char (&)[N]) -> fixed_string<N - 1>;

template<std::size_t L, std::size_t R>
constexpr auto operator+(fixed_string<L> l, fixed_string<R> r) -> fixed_string<L + R>
{
    auto result = fixed_string<L + R>(concat_op(), l , r);
    return result;
};

template<int N> 
constexpr auto to_string()
{
    auto log10 = []
    {
        if constexpr (N < 10)
            return 1;
        else if constexpr(N < 100)
            return 2;
        else if constexpr(N < 1000)
            return 3;
        else 
            return 4; 
            // etc
    };

    constexpr auto len = log10();

    auto result = fixed_string<len>();

    auto pow10 = [](int n, int x)
    {
        if (x == 0)
            return 1;
        else while(x--)
            n *= 10;
        return n;
    };

    auto to_char = [](int n)
    {
        return '0' + char(n);
    };

    int n = N;
    for (int i = 0 ; i < len ; ++i)
    {
        auto pow = pow10(10, i);
        auto digit = to_char(n % 10);
        if (n == 0 && i != 0) digit = ' ';
        result.buffer_[len - i - 1] = digit;
        n /= 10;
    }

    return result;
}

template<int size>
struct MyClass
{
    void onError()
    {
        // obviously won't work but expressing the concatenation like
        // it would be done with a std::string for clarification 
        static const auto errMsg = fixed_string("Error in MyClass of size ") + to_string<size>() + fixed_string(": Detailed error description\n");


        outputErrorMessage (errMsg);
    }
};

int main()
{
    auto x = MyClass<10>();
    x.onError();
}

Results in the following code:

main:
  sub rsp, 8
  mov edi, 56
  mov esi, OFFSET FLAT:MyClass<10>::onError()::errMsg
  call outputErrorMessage(std::basic_string_view<char, std::char_traits<char> >)
  xor eax, eax
  add rsp, 8
  ret

https://godbolt.org/z/LTgn4F

Update:

The call to pow10 is not necessary. It's dead code which can be removed.

like image 4
Richard Hodges Avatar answered Nov 07 '22 06:11

Richard Hodges


Unfortunately your options are limited. C++ doesn't permit string literals to be used for template arguments and, even if it did, literal concatenation happens in the preprocessor, before templates come into it. You would need some ghastly character-by-character array definition and some manual int-to-char conversion. Horrible enough that I can't bring myself to make an attempt, and horrible enough that to be honest I'd recommend not bothering. I'd generate it at runtime, albeit only once (you can make errMsg a function-static std::string at least).

like image 1
Lightness Races in Orbit Avatar answered Nov 07 '22 04:11

Lightness Races in Orbit