C++ Eine etwas kompliziertere union deklaration

rubricanis

Homo ludens
Bei folgendem schnalle ich z.Z. ein wenig ab und komme nicht weiter. In einem Lexer sammle ich die Tokens in einem std::vector und die sollen mit token.emplace_back(...) hinzugefügt werden. Dazu brauch ich aber - wenn ich mich nicht täusche - für Token und die union Token::Value eine Constructor den ich einfach nicht hinbekomme.Könnt ihr mir sagen wie man so etwas macht oder ist die Idee einfach daneben ?

Code:
struct Token {

    enum class Type {
    symbol,
        integer,
        decimal,
        string,
         ....
    };
    
    struct Value{
        // Constructor ???
        Value(Type t, ???  ): type(t), ??? {}

        Type type;
        union {
            std::string    s;
            int i;
            ...
        };
    };

    // Constructor ???
    Token(int _line,int _token, Value val){}

    // values
    int        line;
    int        pos;
   Value    value;
    
};

// the lexer

class Lexer {
        std::vector<Token> token;
public:

    void scan(std::string path){
        ....    // the hard work

        token.emplace_back(line, pos, value)
    }
};
Klar könnt eich das auch einfach mit token.push_back() machen und dann die Werte einzeln eintragen. Aber warum einfach wenn es auch kompliziert geht, das ist ja schließlich C++ und dessen Möglichkeiten sollen doch genutzt werden, oder ? ;)

tia, Peter
 
Wenn Du deinen eigenen Konstruktor wieder weg machst, kannst Du auch den Standardkonstruktor vom struct verwenden.

Allerdings solltest Du enum Type und struct Value außerhalb von dem struct definieren. In-Place nur wenn Du die direkt instanzierst.
 
Kamikaze, ich verstehe nicht was du mit einem Standardkonstruktor meinst. Der Default-Konstruktor initialisiert ja nur die Default-Werte. Aber wie auch immer: Ich habe mich ein wenig eingelesen und umgesehen und hier ist die Lösung. Vielleicht kann ja jemand anderes mal etwas damit anfangen...

Der Trick besteht darin dass (a) für jeden Type ein extra Konstruktor bestehen muss und komplexe Union-Bestandteile (z.B: strings) mit placement new(&) initialisiert werden und (b) dass für diese ein nicht-trivialer Destruktor gebraucht wird.
Code:
struct Token {

     enum class Type {
       symbol,
       string,
       integer,
       .....
     };

     // Constructors

     Token(int l, int p, long i): line(l),pos(p), i(i), type(Type::integer){}

     Token(int l, int p, Type t, std::string s): line(l),pos(p), type(t){
       new (&str) std::string(s);
     }
     Token(const Token& other):line(other.line),pos(other.pos),str(other.str){}

     // Destructor;

     ~Token(){
       using namespace std;
       if( type == Type::symbol || type == Type::string)
         str.~string();
     }
     // Data

     int line;
     int pos;
     union {
       long i;
       std::string   str;
     };
     Type type;  
   };

Jetzt funktioniert
Code:
token.emplace_back(n,t,Token::Type::symbol, s);

Dennoch kommt mir das Ganze eher wie ein Hack vor was kaum im Sinn einer höheren Sprache ist. Aber unions sind num einmal low-level. Dennoch, C++ wird mit Sicherheit nicht meine bevorzugte Sprache. :)
 
Das ist ein struct Du musst den Konstruktor nicht selber schreiben.

Code:
enum class Type {
    symbol,
    integer,
    decimal,
    string,
    ....
};

struct Value{
    int line;
    int pos;
    union {
       long i;
       std::string   str;
    };
    Type type;
};

…
token.emplace_back(n, p, "Foobar"s, Type::symbol)

Und ja, das sieht wie ein übler Hack aus. Keine Ahnung was Du zu erreichen versuchst, aber wahrscheinlich gibt es da einen besseren Weg. Ich würde das union da nicht verwenden.
 
Das ist ein struct Du musst den Konstruktor nicht selber schreiben.
Interessant, wußte ich noch nicht! Mit einer union funktioniert das aber nicht, zumindest nicht unter C++11. Du brauchst zumindes noch einen CopyConstructor und Destructer. Und selbst dann bekomme ich die Fehlermeldung "no matching constructor for initialisation of 'Token'. Aber macht nichts, das schreiben von 1 Dutzend oder so Konstruktoren ist ja kein Problem.
Und ja, das sieht wie ein übler Hack aus. Keine Ahnung was Du zu erreichen versuchst, aber wahrscheinlich gibt es da einen besseren Weg. Ich würde das union da nicht verwenden.
Der Scanner unterscheidet vielleicht wenige Dutzend unterschiedliche Typen und die Typen/Werte müssen a.d. Parser weiter gegeben werden.Ich denke das ist ziemlich effektiv so. Mit Klassen wäre da weit mehr overhead. Und mit templates kommt man da wohl auch nicht weiter, oder? Das bleibt ja auch alles im privaten Bereich vom lexer/parser und wird nur vorübergehend gebraucht.
 
Mit templates ginge das schon wesentlich eleganter, nur da kannst du die Dinger nicht in einen Vektor packen.
Da du aber das type member sparst kannst du einfach noch einen Vektor mit dem type pflegen. Kostet beim prozessieren ein switch mehr.
Dafür ist es denke ich aber auch platzsparender denn bei deinem Union muss man noch SSO von dem string berechnen. Dadurch ist der immer mindestens 24byte groß, was dazu führt dass dein Union auch immer min 24byte groß ist, selbst wenn ein long drin steckt...
 
Meine Empfehlung ist, Du kennst den Typ, solltest die Daten an der Stelle aber als String weitergeben.

Behalte das enum Type und statte das struct mit Casting Operatoren aus:

Code:
#include <cassert>
#include <string>
#include <cstdef>
…

struct Value {
    …
    std::string str;
    Type type;
    operator std::string() const {
        assert(this->type == Type::string);
        return this->str;
    }
    operator int64_t() const {
        assert(this->type == Type::integer);
        return std::stoll(this->str);
    }
    …
};
 
Mit templates ginge das schon wesentlich eleganter, nur da kannst du die Dinger nicht in einen Vektor packen.
Eben! Dann müßte man ein slist oder so nehmen und das wäre noch verschwenderischer. Gerade die Initialisierung mit emplace(..) finde ich wichtig da dann kein zusätzliches allocate notwendig ist..
Dafür ist es denke ich aber auch platzsparender denn bei deinem Union muss man noch SSO von dem string berechnen. Dadurch ist der immer mindestens 24byte groß, was dazu führt dass dein Union auch immer min 24byte groß ist, selbst wenn ein long drin steckt...
Das macht so gut wie nichts, - wenn man es nicht übertreibt. Strings und andere Container sind ja mindestens 1 ptr + 1 ptrdiff_t groß.

Ich denke auch dass Cache-Verhalten hinsichtlich Effizenz wichtiger ist als absolute Größe.
 
Meine Empfehlung ist, Du kennst den Typ, solltest die Daten an der Stelle aber als String weitergeben.

Behalte das enum Type und statte das struct mit Casting Operatoren aus:
Prima Idee! Für den Parser reicht dann oft auch den Enum in einem Switch zu lesen (op_add etc) so dass auf den string nur für die Fehlermeldung zugegriffen werden muss,

Wieder was gelernt! Vielen Dank!

Peter
 
Ein bißchen OT aber der Compiler findet überraschenderweise std::stoll() nicht. Das gleiche gilt für std:: to_string(). Natürlich habe ich <string> included und laut Doku sollten die ja da sein. Die Fehlermeldung lautet " no member named 'stoll' in namespace 'std' ".

Ich benutze clang 3.5.1 unter DragonFly mit -std=c++11.. Ich habe ein wenig rumgesucht aber nichts diesbezügliches gefunden. Was macht man in solchen Fällen?
 
Bei mir (unter FreeBSD) ist das drin, ich würde vermuten das ist die libc++.

Ich denke die liegt unter DF an einem anderen Ort aber grep doch mal darüber:
Code:
# grep stoll /usr/include/c++/v1/string
long long          stoll (const string& str, size_t* idx = 0, int base = 10);
long long          stoll (const wstring& str, size_t* idx = 0, int base = 10);
_LIBCPP_FUNC_VIS long long          stoll (const string& __str, size_t* __idx = 0, int __base = 10);
_LIBCPP_FUNC_VIS long long          stoll (const wstring& __str, size_t* __idx = 0, int __base = 10);
 
Bei mir liegt das für gcc in /usr/include/c++/4.7/ bits/basic_string.h
Code:
inline long long
  stoll(const string& __str, size_t* __idx = 0, int __base = 10)
  { return __gnu_cxx::__stoa(&std::strtoll, "stoll", __str.c_str(),.
Aber mit g++ bekomme ich die gleiche Fehlermeldung.

Clang liegt unter /usr/local/llvm35/clang/include/. Aber da finde ich keinen header mit "string". Ich vermute die greifen auch auf die gcc header zu.

Watt'n Schitt auch!

Ich wollte sowieso demnächst mal clang36 aus den Quellen kompilieren. Mal sehen,vielleicht funktioniert dass ja dann, - sofern es kompiliert. Soweit ich weiss verwendet FreeBSD eine extra angepaßte Version von clang.

Wenn alle Stricke reißen installiere ich eben FreeBSD, - oder doch lieber OpenBSD? ;)
 
Bei C++ ist es wichtig das die STL unabhängig vom Compiler aus der gleichen Quelle kommt, weil bei Templates alle Datenstrukturen zum Interface gehören, der Code der auf den Strukturen arbeitet aber nicht. Will heißen, wenn gcc und clang unterschiedliche Libs verwendeten, könnte es im schlimmsten Fall zur Laufzeit knallen, weil die Datenstrukturen von STL-A mit dem Code von STL-B verwurstet werden.

Wobei ich meine, das clang verhindert Programme auf Basis unterschiedlicher STLs zusammen zu linken. Einfach clang36 zu installieren hilft da wahrscheinlich nicht.
 
Wie an anderer stelle von mir angemerkt ist da unter unter FreeBSD einiges kaputt.
Definiert mal GLIBCXX_USE_C99 vor dem includen, vlt ist das unter DF aehnlich.
 
#define GLIBCXX_USE_C99 nützt nichts. Da ist wohl wirklich der Wurm drin. Ich habe clang36 installiert (war in pkg). Die benutzen offenbar libstdc++-4.7. Die Definition und Namesspaces in ../bits/basic_string.h sind wie folgt:
Code:
namespace std _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION

  inline long long
  stoll(const string& __str, size_t* __idx = 0, int __base = 10)
  { return __gnu_cxx::__stoa(&std::strtoll, "stoll", __str.c_str(),
        __idx, __base); }
Irgend eine Idee? Ich werde mal weiter suchen ...
 
Ich habe mich mal an die DrangonFly mailing-list gewendet und folgende Antowrt bekommen:
stoll and friends should be fixed on the development branch. The only thing "to be done" is upgrade to it, or wait for 4.2 to be released.
Also warten wir mal ab und behelfen uns bis auf weiteres... :rolleyes:
 
Zurück
Oben