3

I am trying to make a compile-time string class. I took a few hints from this post. Unfortunately, I'm stuck on constructor overload precedence: the const char[] constructor is being ignored in favor of the const char* constructor. Any tips would be appreciated!

class string { public: // Can be done compile time. Works lovely! except... template<size_t N> constexpr string(const char(&char_array)[N]) : ptr_(char_array), length_(N-1) {} // This override gets called instead. I *must* keep this constructor. string(const char* const str) : ptr_(str) { length_ = strlen(str); } // Ugly hack. (not acceptable) template<size_t N> constexpr string(const char(&char_array)[N], double unused) : ptr_(char_array), length_(N-1) {} private: const char* ptr_; int length_; }; constexpr const char kConstant[] = "FooBarBaz"; constexpr string kString(kConstant); // Error: constexpr variable 'kString' must be initialized by a constant expression (tries to call wrong overload) constexpr string kString(kConstant, 1.0f); // ugly hack works. 

There's lots of cool things I can do if I can make compile-time string constants.

  • string equality testing is faster on string than const char *
  • Eliminate run-time overhead of implicit conversions from const char * to string that call strlen() on compile-time constant strings.
  • Compile-time string sets that do equality testing instead of hashing for size < N. (this is multiple cpus of overhead on one application I'm looking at)
7
  • If you look at the std::string constructor overloads you will see that they don't even bother with arrays, only pointers. Commented Apr 6, 2016 at 18:43
  • 1
    They also don't support constexpr construction! That's the point. The templatized character array is capable of detecting string size at compile-time. Commented Apr 6, 2016 at 18:45
  • 4
    non-templated function has a preference over templated, even when it requires pointer decay. I do not know how to fix this, though. I had the same problem myself when I tried that, and finally settled on strings based on variadic templates. Commented Apr 6, 2016 at 18:47
  • this code does not compile in gcc 5.1.0 even if I comment out string(const char *) is it problem with compiler? - error: ‘string{((const char*)(& kConstant)), 9}’ is not a constant expression Commented Apr 6, 2016 at 18:59
  • @Slava, compiles for me... Commented Apr 6, 2016 at 19:06

1 Answer 1

2

This is a bit ugly, but it should work:

template<class T, class = std::enable_if_t<std::is_same_v<T, char>>> string(const T * const & str) : ptr_(str) { length_ = strlen(str); } 

The trick is that taking the pointer by const reference blocks array-to-pointer decay during template argument deduction, so when you pass an array, the compiler can't deduce T and the constructor is ignored.

The drawback is that this would also reject other things that are implicitly convertible to const char *.


An alternative might be to accept everything convertible to const char *, and then dispatch based on whether said thing is an array.

template<size_t N> constexpr string(const char(&char_array)[N], std::true_type) : ptr_(char_array), length_(N-1) {} string(const char * str, std::false_type) : ptr_(str) { length_ = strlen(str); } template<class T, class = std::enable_if_t<std::is_convertible_v<T, const char *>>> constexpr string(T&& t) : string(std::forward<T>(t), std::is_array<std::remove_reference_t<T>>()) {} 
Sign up to request clarification or add additional context in comments.

1 Comment

Impressive. I tried something similar but couldn't get it working without the pointer decay trick. +1 for explaining why the const & makes a difference. Unfortunately, I don't think I'll be able to get that past code-review as it complicated the vanilla code-path.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.