web-dev-qa-db-fra.com

utiliser c ++ 11 constexpr pour l'initialisation std :: map

Je veux initialiser un std :: map avec les clés étant une constexpr. Considérez le C++ 11 MWE suivant:

#include <map>
using std::map;

constexpr unsigned int str2int(const char* str, const int h = 0) {
    return !str[h] ? 5381 : (str2int(str, h + 1) * 33) ^ str[h];
}

const map<unsigned int, const char*> values = {
    {str2int("foo"), "bar"},
    {str2int("hello"), "world"}
};

int main() { return 0; }

Tandis que le code compile les clang et gcc récents, le binaire résultant contiendra les chaînes du type de clé: 

 C String Literals

Pourquoi les clés contenues dans le binaire, même si elles sont utilisées comme constexpr? Un moyen de contourner ce problème?

Bien sûr, l'initialisation de la carte aura lieu à l'exécution. Mais les valeurs du binaire ne doivent-elles pas être remplacées par celles de constexpr à compiletime?

Note: Ceci est bien sûr un exemple simplifié. Je sais qu'il existe différentes structures boost qui pourraient mieux convenir à ce cas d'utilisation. Je suis particulièrement intéressé par pourquoi cela se produit.

[Modifier]

Le problème se produit que les optimisations soient activées ou non .. Le code suivant est compilé avec bar étant la seule chaîne définie par l'utilisateur dans la table des chaînes:

#include <map>
#include <iostream>
#include <string>

using namespace std;

constexpr unsigned int str2int(const char* str, const int h = 0) {
  return !str[h] ? 5381 : (str2int(str, h + 1) * 33) ^ str[h];
}

int main() {
  string input;
  while(true) {
    cin >> input;
    switch(str2int(input.c_str())) {
      case str2int("quit"):
      return 0;
      case str2int("foo"):
      cout << "bar" << endl;
    }
  }
}

Pour vérifier les résultats, j'utilisais un petit script shell

$ for x in "gcc-mp-7" "clang"; do 
  $x --version|head -n 1
  $x -lstdc++ -std=c++11 -Ofast constexpr.cpp -o a
  $x -lstdc++ -std=c++1z -Ofast constexpr.cpp -o b
  strings a|grep hello|wc -l
  strings b|grep hello|wc -l
done

gcc-mp-7 (MacPorts gcc7 7.2.0_0) 7.2.0
       1
       0
Apple LLVM version 8.1.0 (clang-802.0.38)
       1
       0
6
muffel

Je ne peux pas reproduire avec g ++ (trunk) ou clang ++ (trunk). J'ai utilisé les drapeaux suivants: -std=c++1z -Ofast. J'ai ensuite vérifié le contenu du binaire compilé avec strings: ni "foo" ni "hello" n'y étaient.

Avez-vous compilé avec les optimisations activées?

Quoi qu'il en soit, votre utilisation de str2int ne force pas l'évaluation au moment de la compilation. Pour le forcer, vous pouvez faire:

constexpr auto k0 = str2int("foo");
constexpr auto k1 = str2int("hello");

const map<unsigned int, const char*> values = {
    {k0, "bar"},
    {k1, "world"}
};
1
Vittorio Romeo

Impossible de reproduire votre problème à l'aide de --std=c++11 -O2 dans GCC 7.2, Clang 5.0 ou MSVC 17.

D&EACUTE;MO

Construisez-vous avec les symboles de débogage sur (-g)? Cela pourrait être ce que vous voyez.

1
rustyx

Déclarer seulement en tant que const ne suffit pas. Les chaînes sont incluses dans le binaire parce que:

const map<unsigned int, const char*> values

est const, mais pas constexpr. Il exécutera «str2int» au démarrage de votre programme, pas au moment de la compilation. Être const ne vous garantira que cela ne permettra pas de nouvelles modifications, mais ne fera aucun compromis sur la compilation.

Il semble que vous recherchiez les conteneurs Frozen constexpr de Serge Sans Paille - https://github.com/serge-sans-paille/frozen

Bien que je ne sache pas si cela fonctionnera avec C++ 11, si vous voulez des gains de performances, ça vaut le coup d'essayer.

Vous pouvez créer des cartes qui sont hachées au moment de la compilation et qui vous donneront l’avantage supplémentaire de produire une fonction de hachage parfaite: vous pourrez accéder à toutes les clés en O(1) heure (temps constant).

C'est un substitut très compétent pour gperf en effet.

Actuellement, Clang et GCC imposent une limite au nombre de clés que vous pouvez traiter au moment de la compilation. Produire une carte avec 2048 clés s’est avéré correct sur mon VPS 1G RAM uniquement avec clang. GCC est actuellement encore pire et va manger tout votre RAM beaucoup plus tôt.

0
zertyz
template<unsigned int x>
using kuint_t = std::integral_constant<unsigned int, x>;

const map<unsigned int, const char*> values = {
  {kuint_t<str2int("foo")>::value, "bar"},
  {kuint_t<str2int("hello")>::value, "world"}
};

cela devrait forcer l'évaluation du temps de compilation.

Dans c ++ 14 c'est un peu moins bavard:

template<unsigned int x>
using kuint_t = std::integral_constant<unsigned int, x>;
template<unsigned int x>
kuint_t<x> kuint{};

const map<unsigned int, const char*> values = {
  {kuint<str2int("foo")>, "bar"},
  {kuint<str2int("hello")>, "world"}
};

et dans c ++ 17 :

template<auto x>
using k_t = std::integral_constant<std::decay_t<decltype(x)>, x>;
template<auto x>
k_t<x> k{};

const map<unsigned int, const char*> values = {
  {k<str2int("foo")>, "bar"},
  {k<str2int("hello")>, "world"}
};

cela fonctionne avec la plupart des constantes de type primitif sans version spécifique au type.

0