Compile time string build function

I want to build a string/char[] in the compile time.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  constexpr char INPUT[] = "abc";  // input char[]


/// Not Working Function
/// But that is what I want
constexpr char* STRING_BUILDER(const char* message) {

    char str[strlen(message) + 3] = "";
    str[0] = static_cast<char>(strlen(message));
    str[1] = ':';
    for (size_t i = 2; i < strlen(message) + 1; ++i) {
        str[i] = message[i - 2];
    }
    str[strlen(message) + 2] = ',';
    return str;
}


I want to a output like:
 
  constexpr char OUTPUT[] = "3:abc,";  // output char[] 

3 is the number of char in the input.

I would also glad to see Modern C++. I think std::string doesnot work in compile time.

Thanks!
Last edited on
if you know it at compile time, just code the result:
constexpr string OUTPUT = "3:abc";

there probably is a way to split up a compile time constant string into another compile time constant string, or to assemble one from bits of others. Is that what you want?

I think std::string doesnot work in compile time.
I think it may if you have small strings? But if it needs to do dynamic allocation, it may not be able? The SSO thing ... not sure if it is that smart but it 'should' be.
Last edited on
I think this is closer, but still not legal:
1
2
3
4
5
6
7
8
constexpr char INPUT[] = "abc";
constexpr char OUTPUT[] = {
    sizeof(INPUT)+'0',
    ':',
    INPUT,  // here I want to insert the contents of INPUT
    ',',
    0    // null terminator
};

You're correct that std::string is not constexpr.

Isn't string_view functionality constexpr?
https://en.cppreference.com/w/cpp/string/basic_string_view

Not sure what limits it will have, syntactically.

Edit: Nevermind, I don't think it will work because even you could only create local char arrays to form a new string_view from, and that string_view would no longer be valid once the scope ends.

Perhaps you could do something creative with string_view::copy and std::array<char> but copy is only constexpr in C++20.
https://en.cppreference.com/w/cpp/string/basic_string_view/copy

___________________________________________________________

Edit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>

template <int N> // N includes the null-terminator
class sb {
  public:
    constexpr sb(const char* c)
    {
        arr[0] = N - 1 + '0';
        arr[1] = ':';
        for (int i = 0; i < N; i++)
        {
            arr[i+2] = c[i];
        }
    }
    
    constexpr const char* c_str()
    {
        return arr;
    }
    
    char arr[N + 2] = {};
};

int main()
{
    std::cout << sb<std::size("hello")>("hello").c_str() << '\n';
}

This was an attempt. This would only would work on lengths < 10. And I don't think it's guaranteed to be done at a compile time. But maybe it will give someone ideas.
Last edited on
If you pair Ganado's code with a macro you can do this: #define STRING_BUILDER(value_to_use) sb<std::size(value_to_use)>(value_to_use).c_str() (not sure if std::size == sizeof, if so, then subtract 1). It is also not very hard to find a full version of compile time int to string on stack overflow.

It might be possible that you might not even need to do compile time strings, and all you might need is stream << value.size() << ":" << value << ",";. Compile time things are quite limited when you want to make it work with something that isn't compile time, and it may be premature optimization. How much source code must you have to have any sort of performance issue even if you just made STRING_BUILDER a function that builds it on the spot?
C++17:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <type_traits>
#include <utility>
#include <iterator>
#include <iostream>

#define O(type)					      \
  template <type N>				      \
    using type##_c = std::integral_constant<type, N>; \
  template <type N>                                   \
    constexpr type##_c<N> type##_v{}                  \
  /**/

O(char); O(unsigned);

#undef O

template <char... Cs>
  constexpr char data_v[sizeof...(Cs)] = { Cs... };

template <char... Cs>
  struct literal_string 
  { 
    constexpr static std::size_t size() noexcept { return sizeof...(Cs); }
    constexpr static char const* data() noexcept { return data_v<Cs...>; }
    constexpr char const& operator[](std::size_t N) const
    { return data()[N]; }
  };

template <char C, char... Cs>
  constexpr auto push_front(char_c<C>, literal_string<Cs...>)
  { return literal_string<C, Cs...>{}; }

namespace detail::string
{
  template <typename S>
    constexpr auto indices_for(S&& s)
    { return std::make_index_sequence<sizeof(s)>(); }

  template <typename MS, std::size_t... Is>
    constexpr auto bind_literal_string(MS m, std::index_sequence<Is...>)
    { return literal_string<m()[Is]...>{}; }
}
  
#define CT_LITERAL_STRING(lit)						  \
  ([]() {								  \
     return detail::string::bind_literal_string				  \
       ([]() -> auto& { return lit; }, detail::string::indices_for(lit)); \
   }())									  \
  /**/

namespace detail::prepend
{
  template <unsigned N, typename Tail>
    struct prepend_number_impl
    {
      using type = prepend_number_impl
        <N / 10, decltype(push_front(char_v<(N % 10) + '0'>, Tail{}))>;
    };

  template <typename Tail> 
    struct prepend_number_impl<0, Tail>
    { using type = Tail; };
}

template <unsigned N, typename Tail>
  using prepend_number_t = typename detail::prepend::prepend_number_impl
    <N / 10, decltype(push_front(char_v<(N % 10) + '0'>, Tail{}))>::type;

template <unsigned N, typename Tail>
  constexpr auto prepend_number(unsigned_c<N>, Tail)
  { return prepend_number_t<N, Tail>{}; }

template <typename String>
  constexpr auto encoded_string(String s)
  { 
    return prepend_number
      (unsigned_v<s.size() - 1>, push_front(char_v<':'>, s)); 
  }

int main()
{
  constexpr auto es = encoded_string(CT_LITERAL_STRING("foobar"));
  std::cout << es.data() << '\n';
}

Demo:
http://coliru.stacked-crooked.com/a/8aa6d6cd2d222e81

It's very possible to do better....
Last edited on
mbozzi shows a way to do it, but I'd consider just building it at runtime. There comes a point where the complexity isn't worth it.
Here's a very sloppy but still much nicer version in the value domain:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include <array>
#include <stdexcept>
#include <iostream>
#include <cstddef>

// Thanks cppreference
// https://en.cppreference.com/w/cpp/algorithm/copy_backward
template< class BidirIt1, class BidirIt2 >
constexpr BidirIt2 copy_backward(BidirIt1 first, BidirIt1 last, BidirIt2 d_last)
{
    while (first != last) {
        *(--d_last) = *(--last);
    }
    return d_last;
}

struct literal_string
{
  constexpr static std::size_t max_size = 32;

  template <std::size_t N>
    constexpr literal_string(char const(&str)[N])
      : data_()
      , size_(N - 1) 
    {
      for (std::size_t i = 0; i < N; ++i)
        data_[i] = str[i];
    }
  
  constexpr char const* data() const { return data_; }
  constexpr std::size_t size() const { return size_; }

  constexpr void push_front(char c)
  {
    if (size_ + 1 >= max_size)
      throw std::out_of_range{"maximum size exceeded in literal_string"};
    
    copy_backward(data_, data_ + size_, data_ + size_ + 1);
    data_[0] = c;
    size_++;
  }

  constexpr char& operator[](std::size_t i)
  { 
    if (i >= max_size)
      throw std::out_of_range{"out-of-bounds access in literal_string"};
    return data_[i];
  }

  constexpr char const& operator[](std::size_t i) const
  {
    if (i >= max_size)
      throw std::out_of_range{"out-of-bounds access in literal_string"};
    return data_[i];
  }

private:
  char data_[max_size];
  std::size_t size_;
};

constexpr literal_string encode_string(literal_string test)
{
  auto size = test.size();
  test.push_front(':');
  do test.push_front((size % 10) + '0'); while (size /= 10);
  return test;
}

int main()
{
  constexpr literal_string foo = encode_string("blah blah blah");
  std::cout << foo.data() << '\n';
}

Demo:
http://coliru.stacked-crooked.com/a/c18e6a15b73b3af8
Last edited on
Topic archived. No new replies allowed.