3 votes

Évaluation constexpr à l'intérieur d'un constructeur

Je développe ma propre classe de chaîne de caractères qui possède à la fois une optimisation pour les petites chaînes et un indicateur interne pour savoir si la chaîne est Ascii, UTF8, WTF8 ou une chaîne d'octets. Le constructeur

String(const char*);

peut être utilisé pour construire soit une chaîne Ascii, soit une chaîne UTF8. Il ne devrait être utilisé qu'avec des littéraux tels que :

const String nom_de_famille = "Fayard"
const String prénom = "François"

Le constructeur doit calculer à la fois la longueur de la chaîne et vérifier si elle est Ascii ou UTF8. Par conséquent, j'ai écrit ces fonctions pour qu'elles puissent être évaluées au moment de la compilation.

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(*s) & 0x80_uchar) ==
             0x00_uchar) && isAscii(s + 1));
}

Le constructeur est écrit de cette manière et est disponible dans les en-têtes pour qu'il puisse être inline.

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

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

Mais je n'arrive pas à faire en sorte que les fonctions size et isAscii soient évaluées au moment de la compilation (j'ai essayé et vérifié l'assembly avec gcc 4.8.5, clang 4.0.1, icpc 17.0.4). Y a-t-il un moyen de le faire?

PS : La solution doit être uniquement en C++11 et compilable avec gcc 4.8.5 et Visual Studio 2015.

3 votes

Eh bien, vous donnez ces fonctions des données runtime, alors comment pensez-vous que le compilateur devrait être capable de les évaluer au moment de la compilation ?

0 votes

Avez-vous essayé de déplacer le reste du code de votre constructeur dans une fonction séparée ?

0 votes

@zett42 : Je ne comprends pas ce que vous voulez dire par données d'exécution. Les fonctions size("Hello") et isAscci("François") sont évaluées au moment de la compilation. Pourquoi ne peuvent-elles pas être évaluées au moment de la compilation dans le constructeur lorsque celui-ci est en ligne ?

2voto

Öö Tiib Points 4755

Vous pouvez utiliser enable_if pour contraindre votre constructeur à ne pas prendre plus de 22 caractères :

template::type = 0> 
constexpr String(const char(&string_literal)[N]) { /*...*/ }

0 votes

Ça semble bien. Je suis rentré chez moi car il est 2h du matin en France. Je vais essayer demain matin.

0 votes

Il échoue avec gcc 4.8.5 mais fonctionne avec clang++ 4.0.1 et -std=c++11. Le corps du constructeur contient un std::memcpy qui n'est pas autorisé par gcc 4.8.5. J'ai le sentiment que même si clang l'accepte en tant que C++11, il a besoin de C++14 pour compiler. Quoi qu'il en soit, j'ai besoin de la compatibilité avec gcc 4.8.5 car il s'agit d'une bibliothèque scientifique et RHEL 7, qui est le "standard" sur les clusters, est livré avec gcc 4.8.5.

2voto

Jarod42 Points 15729

Les arguments de la fonction ne sont pas constexpr, donc vous ne pouvez pas propager la chaîne littérale.

Une façon de faire est de convertir la chaîne littérale en séquence de caractères :

template 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 constexpr C Chars::str[1 + sizeof...(cs)];

// Nécessite une extension GNU
template 
constexpr Chars operator""_cs() { return {}; }

Sans l'extension gnu, vous devez utiliser une MACRO pour transformer la littérale en séquence de caractères, comme je le fais ici.

Ensuite, vous avez toutes les informations de valeur des types :

template 
constexpr il::int_t size(Chars) {
  return sizeof...(Cs);
}

template 
constexpr bool isAscii(Chars) {
    // Expression de pliage C++17
    return ((static_cast(Cs) & 0x80_uchar) == 0x00_uchar && ...);
}

ou pour C++11 :

template 
constexpr bool isAscii(Chars) { return true; }

template 
constexpr bool isAscii(Chars) {
    // Expression de pliage C++17
    return ((static_cast(Head) & 0x80_uchar) == 0x00_uchar
           && isAscii(Chars{});
}

0 votes

Merci, mais j'ai besoin de compatibilité avec gcc 4.8.5 et donc C++14/C++17 n'est pas autorisé.

0 votes

@InsideLoop: J'étais paresseux pour implémenter isAscii en mode C++11. Je vais l'ajouter.

1voto

bolov Points 4005

C'est essentiellement l'idée de @ Öö Tiib, mais étendue pour montrer qu'elle fonctionne dans gcc 4.8.5 comme vous l'avez dit que vous le demandez :

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

  template ::type = nullptr>
  constexpr String(const char (&str)[N])
    : size_{size(str)},
      data_{}
  {
  }

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

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

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

Vous ne pouvez pas avoir un constexpr contenant malloc, il n'y a pas de solution et il semble que cela ne le sera jamais. J'ai lu une discussion sur l'introduction d'un constexpr_vector dans la norme, mais permettre l'accès à la mémoire aléatoire en contexte constexpr sera extrêmement délicat car constexpr doit détecter et échouer sur chaque UB possible et il est donc très probable qu'il ne sera pas pris en charge dans un avenir prévisible.

Mais vous pouvez avoir un constructeur de chaîne courte constexpr comme je vous l'ai montré. Vérifiez-le sur godbolt avec gcc 4.8.5


Vous avez dit que vous voulez initialiser une variable de pile. Par là, je pense que vous voulez dire une variable de stockage automatique. Oui, cela peut être fait en C++11 :

template  struct Seq{};

template 
struct Make_seq_impl
{
    using Type = typename Make_seq_impl::Type;
};

template 
struct Make_seq_impl
{
    using Type = Seq;
};

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

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

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

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

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', "");
}

_

Je vous laisse le soin de combiner les 2 méthodes.

_

0 votes

Le problème est que le petit constructeur doit copier le contenu de str dans un data_ alloué sur la pile. Il semble avoir besoin de C++14 pour faire ce genre de chose, ce qui exclut gcc 4.8.5.

0 votes

Tu veux dire que tu as besoin de copier str dans data_?

0 votes

Vous pouvez à un tableau c'est-à-dire char small_data[max_small_string]. Quand j'aurai un peu de temps, je vais cuisiner une solution

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X