构造函数中的Constexpr评估

时间:2017-08-06 23:06:26

标签: c++ constexpr

我开发了自己的字符串类,它既有小字符串优化,又有内部标志,可以知道字符串是Ascii,UTF8,WTF8还是字节字符串。构造函数

String(const char* );

可用于构造Ascii字符串或UTF8字符串。它只应与文字一起使用,例如:

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

构造函数需要计算字符串的长度并检查它是否为Ascii或UTF8。因此,我编写了这些函数,以便在编译时对它们进行评估。

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));
}

构造函数是这样写的,并且在标题中可用,因此可以内联。

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

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

但是我无法设法获得函数大小并且在编译时评估isAscii运行(尝试使用gcc 4.8.5,clang 4.0.1,icpc 17.0.4检查程序集)。有没有办法做到这一点?

PS:解决方案只需要是C ++ 11,并使用gcc 4.8.5和Visual Studio 2015进行编译。

3 个答案:

答案 0 :(得分:2)

您可以使用enable_if来限制构造函数,使其不超过22个字符:

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

答案 1 :(得分:2)

函数的参数不是constexpr,因此你不能传播字符串文字。

一种方法是将文字字符串转换为字符序列:

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 {}; }

如果没有gnu扩展名,你必须使用一些MACRO将文字转换为字符序列,就像我there一样。

然后您拥有类型的所有价值信息:

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 && ...);
}

或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 :(得分:1)

这基本上是@ÖöTiib的ideea,但扩展为显示它在gcc 4.8.5中工作,如您所说:

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"};
}

你不能拥有一个包含malloc的constexpr,没有办法解决它,它看起来永远不会。我读到了关于在标准中引入constexpr_vector的一些讨论,但是在constexpr上下文中允许随机内存访问将非常棘手,因为constexpr需要在每个可能的UB上检测并失败,因此很可能不支持它在可预见的未来。

但是你可以有一个constexpr小字符串构造函数,就像我给你看的那样。在godbolt with gcc 4.8.5

上查看

您说要初始化堆栈变量。我认为你的意思是自动存储。是的,可以在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', "");
}

我把它留给你结合两种方法。