Skip to content

Instantly share code, notes, and snippets.

@ericniebler
Created December 10, 2016 04:27
Show Gist options
  • Save ericniebler/a63c8729f33d7a5e70104dc39af0209a to your computer and use it in GitHub Desktop.
Save ericniebler/a63c8729f33d7a5e70104dc39af0209a to your computer and use it in GitHub Desktop.
An almost perfect design for a hybrid constexpr/runtime string
// Copyright Eric Niebler 2016
#include <cstddef>
#include <cstdio>
#include <cstdint>
#include <cstring>
#include <utility>
#include <type_traits>
#define REQUIRES(X) \
typename std::enable_if<(X), int>::type = 0
using std::size_t;
constexpr char const* find_null(char const *z) {
return *z ? find_null(z+1) : z;
}
constexpr size_t constexpr_strlen(char const *z) {
return find_null(z) - z;
}
template <class T>
constexpr T constexpr_min(T a, T b) {
return b < a ? b : a;
}
template <class T>
constexpr T constexpr_max(T a, T b) {
return b < a ? a : b;
}
template <size_t N>
constexpr std::make_index_sequence<N>* indices() {
return nullptr;
}
enum class which_rep : char { small, big };
template <size_t N>
class cstring {
struct bigdata {
char* c_str_;
std::uint32_t size_;
char dummy_;
};
static constexpr size_t M =
constexpr_max(N + 1, sizeof(bigdata));
using smalldata = char[M];
union {
smalldata smalldata_;
bigdata bigdata_;
};
template <class T>
static constexpr char get_at_(T const &t, size_t i, size_t size) {
return i < size ? t[i] : i == M-1 ? (char)((unsigned char)(M-1 - size)) : '\0';
}
constexpr which_rep which_() const noexcept {
return static_cast<unsigned char>(smalldata_[M-1]) ==
static_cast<unsigned char>((char)-1)
? which_rep::big
: which_rep::small;
}
template <class T, size_t... Is>
constexpr cstring(T const &t, size_t size, std::index_sequence<Is...>*) noexcept
: smalldata_{get_at_(t, Is, size)...} {}
static constexpr char s_empty[1]{};
public:
constexpr cstring() noexcept : cstring{s_empty, 0, indices<M>()} {}
template <size_t X, REQUIRES(X <= M && M < 255)>
constexpr cstring(char const (&that)[X]) noexcept
: cstring{that, X-1, indices<M>()} {
}
template <size_t X, REQUIRES(X > M || M >= 255)>
cstring(char const (&that)[X]) noexcept(false)
{
smalldata_[M-1] = (char)-1;
bigdata_.size_ = X-1;
bigdata_.c_str_ = strdup(that);
}
// Gah! this makes the type non-literal; can't constexpr construct!
// ~cstring() noexcept {}
constexpr char const * c_str() const noexcept {
return which_() == which_rep::small ? smalldata_ : bigdata_.c_str_;
}
constexpr std::size_t size() const noexcept {
return which_() == which_rep::small
? static_cast<size_t>(M-1 - static_cast<unsigned char>(smalldata_[M-1]))
: bigdata_.size_;
}
constexpr char const& operator[](std::size_t i) const noexcept {
return which_() == which_rep::small
? smalldata_[i]
: bigdata_.c_str_[i];
}
constexpr bool is_small() const {
return which_() == which_rep::small;
}
};
int main() {
static constexpr cstring<23> s{"hello world hello world"};
constexpr char const& c = s[3];
static_assert(s.size() == 23u, "");
static_assert(sizeof(s) == 24u, "");
static_assert(s[s.size()] == '\0', "");
static_assert(s.is_small(), "");
std::printf("\"%s\"\n", s.c_str());
static constexpr cstring<0> s2;
constexpr char const& c2 = s2[0];
static_assert(s2.size() == 0u, "");
static_assert(sizeof(s2) == 16u, "");
static_assert(s2[s2.size()] == '\0', "");
static_assert(s2.is_small(), "");
std::printf("\"%s\"\n", s2.c_str());
cstring<5> b{"hello world!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"};
std::printf("\"%s\"\n", b.c_str());
if(!b.is_small()) {
std::printf("Is big! Size = %d\n", (int)b.size());
}
}
@JavierJF
Copy link

JavierJF commented Dec 14, 2016

Yes, and if that helper function is a so-called deduction guide (coming in C++17)

Oh! I didn't read about that yet! It's a really interesting feature, and I also agree with that pushes this really close to what you wanted. At least it will be nice and clean syntactically.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment