it-swarm.com.de

verwenden Sie c ++ 11 constexpr für die Initialisierung von std :: map

Ich möchte eine std :: map mit den Schlüsseln, die eine constexpr sind, initialisieren. Betrachten Sie das folgende C++ 11-MWE:

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

Während der Code das letzte clang und gcc kompiliert, enthält die resultierende Binärdatei die Zeichenfolgen des Schlüsseltyps: 

 C String Literals

Warum sind die Schlüssel in der Binärdatei enthalten, obwohl sie als Constexpr verwendet werden? Wie kann ich dieses Verhalten umgehen?

Natürlich wird die Karteninitialisierung zur Laufzeit durchgeführt. Aber sollten die Werte in der Binärdatei nicht zur Zeit der Kompilierung durch die Constexpr-Werte ersetzt werden?

Hinweis: Dies ist natürlich ein vereinfachtes Beispiel. Ich weiß, dass es verschiedene boost -Strukturen gibt, die für diesen Anwendungsfall besser geeignet sind. Ich interessiere mich besonders für warum dies passiert.

[Bearbeiten]

Das Verhalten tritt unabhängig davon auf, ob Optimierungen aktiviert sind oder nicht. Der folgende Code wird kompiliert, wobei bar die einzige benutzerdefinierte Zeichenfolge in der Zeichenfolgentabelle ist:

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

Zur Überprüfung der Ergebnisse verwendete ich ein kleines Shell-Skript

$ 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

Ich kann weder mit g ++ (trunk) noch mit clang ++ (trunk) reproduzieren. Ich habe die folgenden Flags verwendet: -std=c++1z -Ofast. Ich habe dann den Inhalt der kompilierten Binärdatei mit strings überprüft: Es waren weder "foo" noch "hello" vorhanden.

Haben Sie mit aktivierten Optimierungen kompiliert?

Unabhängig davon erzwingt Ihre Verwendung von str2int keine Auswertung während der Kompilierung. Um dies zu erzwingen, können Sie Folgendes tun:

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

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

Das Problem kann nicht mithilfe von --std=c++11 -O2 in GCC 7.2, clang 5.0 oder MSVC 17 reproduziert werden.

DEMO

Bauen Sie mit Debug-Symbolen auf (-g)? Das könnte das sein, was Sie sehen.

1
rustyx

Nur als const deklarieren reicht nicht aus. Die Zeichenfolgen sind in der Binärdatei enthalten, weil:

const map<unsigned int, const char*> values

ist const, aber nicht constexpr. Es wird 'str2int' ausgeführt, wenn Ihr Programm startet, nicht zur Kompilierzeit. Wenn Sie const sind, können Sie nur garantieren, dass Sie keine weiteren Änderungen zulassen, aber keine Kompromisse beim Kompilieren eingehen.

Es scheint, Sie suchen nach den Froste Constexpr-Containern von Serge Sans Paille - https://github.com/serge-sans-paille/frozen

Ich weiß zwar nicht, ob es mit C++ 11 funktioniert, aber wenn Sie Leistungssteigerungen wünschen, ist es auf jeden Fall einen Versuch wert.

Sie können Karten erstellen, die zur Kompilierzeit gehasht werden, und Sie haben den zusätzlichen Vorteil, dass Sie eine perfekte Hash-Funktion erstellen. So können Sie auf alle Schlüssel in O(1) time (konstante Zeit) zugreifen.

Es ist in der Tat ein sehr kompetenter Ersatz für Gperf.

Clang und GCC setzen derzeit ein Limit für die Anzahl der Schlüssel, die Sie zur Kompilierzeit bearbeiten können. Das Erstellen einer Karte mit 2048 Schlüsseln erwies sich auf meinem 1G RAM VPS nur mit Lärm als OK. GCC ist derzeit noch schlechter und wird all Ihre RAM viel früher essen.

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

das sollte die Kompilierzeitauswertung erzwingen.

In c ++ 14 ist es etwas weniger ausführlich:

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

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

es funktioniert mit den meisten primitiven Typkonstanten ohne typspezifische Version.