Constexpr evaluation inside constructor

I am developing my own string class that has both a little string optimization and an internal flag to see if the string is Ascii, UTF8, WTF8, or a byte string. Constructor

String(const char* );

      

can be used to construct an Ascii string or UTF8 string. It should only be used with literals such as:

const String last_name = "Fayard"
const String first_name = "François"

      

The constructor has to calculate the length of the string and check if it is Ascii or UTF8. So I wrote these functions so that they can be evaluated at compile time.

inline constexpr il::int_t size(const char* s) {
  return (*s == '\0') ? 0 : (size(s + 1) + 1);
}

inline constexpr bool isAscii(const char* s) {
  return (*s == '\0')
         ? true
         : (((static_cast<unsigned char>(*s) & 0x80_uchar) ==
             0x00_uchar) && isAscii(s + 1));
}

      

The constructor is written this way and is available in headers so that it can be inlined.

String(const char* data) {
  const int n = size(data);
  const bool ascii = isAscii(data);

  if (n <= max_small_string) {
    ...
  } else {
    data_ = malloc();
    ...
  }
}

      

But I am unable to get the size of the functions and run isAscii at compile time (try and check the build with gcc 4.8.5, clang 4.0.1, icpc 17.0.4). Is there a way to do this?

PS: The solution should be C ++ 11 only and compile with gcc 4.8.5 and Visual Studio 2015.

+3


source to share


3 answers


It's basically @ Öö Tiib ideea, but expanded to show that it works in gcc 4.8.5

, as you said you need:

struct String
{
  static const int max_small_string = 10;
  int size_;
  char* data_;

  template <int N,
      typename std::enable_if<(N <= String::max_small_string), void*>::type = nullptr>
  constexpr String(const char (&str)[N])
    : size_{size(str)},
      data_{}
  {
  }

  template <int N,
    typename std::enable_if<(N > String::max_small_string), void*>::type = nullptr>
  String(const char (&str)[N])
    : size_{size(str)},
       data_{static_cast<char*>(malloc(size_))}
  {
  }
};


auto foo() -> void
{
  constexpr String ss = String{"asd"}; // OK, constexpr

  String hs =  String{"a sdjwq niornyuqe rniehr iwhtR Trtj rjtsd asde"};
}

      

You can't have a constexpr containing it malloc

, there is no way around it, and it looks like it never did. I read about some discussion about introducing the standard constexpr_vector

into the standard, but allowing arbitrary memory access in the context of constexpr will be extremely difficult because constexpr has to detect and fail on all possible UBs, and so it will most likely not be supported in the foreseeable future. future.

But you can have a small string constructor constexpr

as I showed you. Check it out on godbolt with gcc 4.8.5




You said you want to initialize a stack variable. I think you mean automatic storage. Yes it can be done in C ++ 11:

template <int... Is> struct Seq{};

template <int I, int Max, int... Is>
struct Make_seq_impl
{
    using Type = typename Make_seq_impl<I + 1, Max, Is..., I>::Type;
};

template <int Max, int... Is>
struct Make_seq_impl<Max, Max, Is...>
{
    using Type = Seq<Is...>;
};

template <int N>
using Make_seq = typename Make_seq_impl<0, N>::Type;

struct X
{
    static const int max_size_ = 10;
    char data_[max_size_];

    template <int N, int... Is>
    constexpr X(const char (&str)[N], Seq<Is...>)
        : data_ {(Is < N ? str[Is] : '\0')...}
    {
    }

    template <int N>
    constexpr X(const char (&str)[N])
        : X(str, Make_seq<max_size_>{})
    {
    }
};

auto test() -> void
{
    constexpr X x{"Asd"};

    static_assert(x.data_[0] == 'A', "");
    static_assert(x.data_[1] == 's', "");
    static_assert(x.data_[2] == 'd', "");
    static_assert(x.data_[3] == '\0', "");
}

      

I left it for you to combine the 2 methods.

+1


source


You can use enable_if to limit your constructor to no more than 22 characters:



template<size_t N, typename std::enable_if<(N <= 22), int>::type = 0> 
constexpr String(const char(&string_literal)[N]) { /*...*/ }

      

+2


source


the function argument is not constexpr, so you cannot propagate a string literal.

One way is to turn a literal string into a char sequence:

template<typename C, C...cs> struct Chars
{
    using str_type = C[1 + sizeof...(cs)];
    static constexpr C str[1 + sizeof...(cs)] = {cs..., 0};

    constexpr operator const str_type&() const { return str; }
};

template<typename C, C...cs> constexpr C Chars<C, cs...>::str[1 + sizeof...(cs)];

// Requires GNU-extension
template <typename C, C...cs>
constexpr Chars<C, cs...> operator""_cs() { return {}; }

      

Without the gnu extension, you need to use some MACRO to convert the literal to a char sequence as I do there .

Then you have all the information about the value from the types:

template <typename C, C ... Cs>
constexpr il::int_t size(Chars<C, Cs...>) {
  return sizeof...(Cs);
}

template <typename C, C ... Cs>
constexpr bool isAscii(Chars<C, Cs...>) {
    // C++17 folding expression
    return ((static_cast<unsigned char>(Cs) & 0x80_uchar) == 0x00_uchar && ...);
}

      

or for C ++ 11:

template <typename C>
constexpr bool isAscii(Chars<C>) { return true; }

template <typename C, C head, C ... Cs>
constexpr bool isAscii(Chars<C, Cs...>) {
    // C++17 folding expression
    return ((static_cast<unsigned char>(Head) & 0x80_uchar) == 0x00_uchar
           && isAscii(Chars<C, Cs...>{});
}

      

+2


source







All Articles