2016-07-12 8 views
3

Ich brauche zwei std::map s, deren Werte aus Leistungsgründen auf die Elemente der anderen verweisen.
Dies ermöglicht das konstante Einfügen/Entfernen von Elementen, wenn das andere bereits durchlaufen wurde.Zwei std :: maps, deren Werte die Iteratoren des jeweils anderen sind?

Was ist der schnellste Weg, um dies korrekt in C++ zu implementieren? Beachten Sie, dass der offensichtliche Ansatz nicht funktioniert, da der Typ der zweiten Map unvollständig ist, bevor ihr Iterator als der Typ des Werts der ersten Map deklariert werden kann. Sind Variants meine einzige Option, oder gibt es eine bessere Lösung?

+0

müssen sie Iteratoren sein? könnten sie einfach Hinweise auf den Wert sein? –

+0

@RichardHodges: Können Zeiger die Einschränkungen erfüllen, die ich im zweiten Satz angegeben habe? – Mehrdad

+0

schwierig ohne weitere Informationen zu sagen. Ein void-Zeiger auf den value_type könnte umgewandelt werden, um einen Schlüssel zu erhalten .... aber ich weiß, dass das nicht das ist, was Sie wollen. Vielleicht ist es Zeit, Bedenken zu spalten? Lagerung und Indexierung? –

Antwort

2

Sie wollen gegenseitig rekursive Typen in einer Weise, die, soweit ich weiß, nicht mit (forward) Deklarationen erreicht werden kann. Aber C++ bietet ein weiteres Mittel, das die berühmte CRTP ist:

#include <map> 

template <typename T> 
struct BidirMapHelper { 
    struct ReverseElt { 
     ReverseElt() {} 
     ReverseElt(typename T::iterator p) : v(p) {} 
     typename T::iterator v; 
    }; 
    typedef std::map<int, ReverseElt> ReverseMap; 
}; 

struct BidirMap: BidirMapHelper<BidirMap> 
{ 
    struct DirectElt { 
     DirectElt() {} 
     DirectElt(ReverseMap::iterator p) : v(p) {} 
     ReverseMap::iterator v; 
    }; 
    typedef std::map<int, DirectElt> DirectMap; 
    typedef DirectMap::iterator iterator; 
}; 

typedef BidirMap::DirectMap DirectMap; 
typedef BidirMap::ReverseMap ReverseMap; 

int main() { 
    DirectMap m1; 
    ReverseMap m2; 
    m1[0] = m2.end(); 
    m2[0] = m1.end(); 
    return 0; 
} 

Dies kompiliert sauber mit g ++ und Klirren ++ unter Linux, aber ich muss zugeben, dass ich nicht sicher bin, es hängt nicht von einer Implementierung Merkmal der Standardbibliothek wie zum Beispiel SCARY-Iteratoren.

+0

+1, das ist extrem clever und die Art von Lösung, die ich mir erhofft hatte, aber ich bin ziemlich überrascht, dass dies sauber kompiliert. Ist 'typename BidirMap :: iterator 'nicht definiert, wenn der Typ' BidirMapHelper ' in 'ReverseElt' instanziiert wird? Oder ist die Instanziierung des Typs verzögert, weil er in 'ReverseElt' gehüllt ist? – Mehrdad

+0

Dieses Problem ist nicht anders als in den meisten Anwendungen des CRTP, die von der Verfügbarkeit während der Instanziierung einer Vorlage des Parameters abhängt. Aber je mehr ich darüber nachdenke, desto mehr bin ich davon überzeugt, dass es von implementierten definierten Eigenschaften von std :: map abhängt, um die Entkopplung bereitzustellen, die benötigt wird, um es funktionieren zu lassen (normalerweise wird die Entkopplung durch die Tatsache, dass Mitgliederfunktionskörper in Betracht gezogen werden, bereitgestellt nur wenn die Klasse nicht mehr unvollständig ist). – AProgrammer

+0

In normalen CRTP ist die Verwendung der abhängigen Typen innerhalb der Member-Funktionen, und wie Sie sagten, werden diese nicht instanziiert, bis sie verwendet werden, zu welchem ​​Zeitpunkt der Typ abgeschlossen ist. Hier ist es in den Member-Deklarationen, deshalb bin ich verwirrt, warum es funktionieren sollte ... – Mehrdad

1

Hier ist eine einfachere Lösung, die jedoch nicht für eine beliebige normkonforme Implementierung von std::map kompiliert werden kann. Dennoch kompiliert es mit gcc 4.4.7 (was nicht einmal -std = C++ 11 unterstützt) und mit gcc 5.x, gcc 6.1, clang 3.x und icc 13.0.1 (sowohl mit als auch ohne -std = C++ 11).

#include <map> 

struct It1; 
struct It2; 

typedef std::map<int, It2> Map1; 
typedef std::map<int, It1> Map2; 

struct It2 : Map2::iterator 
{ 
    It2() {} 
    It2(Map2::iterator it) : Map2::iterator(it) {} 
}; 

struct It1 : Map1::iterator 
{ 
    It1() {} 
    It1(Map1::iterator it) : Map1::iterator(it) {} 
}; 

int main() 
{ 
    Map1 m1; 
    Map2 m2; 

    m1[0] = m2.end(); 
    m2[0] = m1.end(); 
    return 0; 
} 
+0

Es funktioniert mit GCC, ist aber technisch undefiniert Verhalten, weil Sie 'std :: map' mit einem unvollständigen Typ als Vorlage instanziieren Streit. –

+0

@ JonathanWakely Ich würde hier nicht den Begriff * undefined Verhalten * verwenden. Vielleicht ist es laut dem Standard nicht kompiliert, aber das ist nicht UB in seinem gefährlichen Sinne. Wenn es kompiliert, sollte es ohne Problem funktionieren. – Leon

+0

Wie gesagt, es ist ** technisch ** undefiniertes Verhalten. –