std::map::emplace: GCC vs. MSVC
-
Hallo,
ich möchte portablen Code schreiben und habe ein Problem mit map emplace():
class TestX1 { int x; public: TestX1(const TestX1&) = delete; TestX1(int x) : x(x) {} ~TestX1() {}; }; int main() { std::map<int, TestX1> m; m.emplace(42, 42); // Variante 1: GCC error, MSVC OK m.emplace(std::piecewise_construct, std::make_tuple(42), std::make_tuple(42)); // Variante 2: beide OK }
Mit MSVC sind beide emplace Varianten OK.
Doch der GCC 5.2.1 meckert bei Variante 1:stl_pair.h(134,45): error : use of deleted function 'TestX1::TestX1(const TestX1&)'
: first(std::forward<_U1>(__x)), second(__y) { }// stl_pair.h:
template<class _U1, class = typename
enable_if<is_convertible<_U1, _T1>::value>::type>
constexpr pair(_U1&& __x, const _T2& __y)
: first(std::forward<_U1>(__x)), second(__y) { }Warum ist das so? Was macht GCC anders? Muss ich jetzt überall Variante 2 nehmen, oder gibt es andere Möglichkeiten?
Dankesehr!
-
Microsoft machte schon immer "mehr" und manchmal auch bissi anders als der Standard ...
Am besten Du schaust im aktuellen Standard nach, was gehen muss/soll.
Dann schausst ob MS und die anderen das so akzeptieren.Wenn es keinen gemeinsammen Nenner gibt, musst mit compiler-weichen arbeiten, also auf preprozessor ebene das system abfragen und zum richtigeh code verzweigen.
Hier noch mal die überisicht, wo draus vorgehen sollte, das der gcc doch recht nahe am Standard ist.
Wobei der Standard das minimum ist, mehr zerstört nicht die standard Konformität sondern macht nur abhängig ^^
http://en.cppreference.com/w/cpp/compiler_supportCiao ...
-
RHBaum schrieb:
Am besten Du schaust im aktuellen Standard nach, was gehen muss/soll.
Für jede kleine Funktion?
Könnte es theoretisch auch zu Laufzeitproblemen kommen (allgemein)? Das wäre jaHabe ich in meinem Fall nur die make_tuple Möglichkeit?
-
ReEval schrieb:
Warum ist das so? Was macht GCC anders? Muss ich jetzt überall Variante 2 nehmen, oder gibt es andere Möglichkeiten?
Du könntest TestX1 einen move-constructor verpassen.
-
Das wäre im Standard C++ Forum wohl besser aufgehoben. Denn libstdc++ verhält sich hier nicht (mehr!) Standardkonform, und libc++ genausowenig. Laut Tabelle 101 konstruiert der
emplace
-Aufruf nämlich daspair<>
Objekt perforward
. Folgendes ist ein reduziertes Beispiel dass die Diskrepanz aufzeigt:std::pair<int, TestX1> p(0, 0);
Der relevante perfect-forwarding Konstruktor soll seit der Aufnahme von N4387 (welche bereits in N4527 vorhanden ist) nicht ins candidate set
N4527 [pairs.pair]/8 schrieb:
…unless
is_constructible<first_type, U&&>::value
istrue
andis_constructible<second_type, V&&>::value
istrue
.Ein entsprechendes
static_assert
geht sogar völlig ungehindert durch:static_assert( std::is_constructible<TestX1, int&&>{} );
is_constructible
prüft nämlich direct-initialization, i.e. grob übersetzt, die Gültigkeit des AusdrucksTestX1(std::move(0))
, was natürlich in Ordnung ist.
Jedoch verwenden die Konstruktoren von e.g. libc++ nichtis_constructible
sondernis_convertible
, wie es nämlich wie nach der Aufnahme von N3140 (in N4296 zu finden) korrekt war:N4296 [pairs.pair]/9 schrieb:
Remarks: If
U
is not implicitly convertible tofirst_type
orV
is not implicitly convertible tosecond_type
this constructor shall not participate in overload resolution.is_convertible
prüft implizite Konvertierungen, wie in [conv]/3 definiert:An expression
e
can be implicitly converted to a typeT
if and only if the declarationT t=e;
is well-formed, for some invented temporary variablet
(8.5).Allerdings ist hier natürlich ein gravierender Unterschied; copy-initialization von
TestX1
via e.g.TestX1 t = 0; // Hier 0 weil U/V keine Referenzen sind und wir daher ein prvalue brauchen
impliziert eine temporary:
[dcl.init]/(17.6.2) schrieb:
Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type […] are enumerated as described in 13.3.1.4, and the best one is chosen through overload resolution (13.3). […] The function selected is called with the initializer expression as its argument; if the function is a constructor, the call initializes a temporary of the cv-unqualified version of the destination type. The temporary is a prvalue. The result of the call (which is the temporary for the constructor case) is then used to direct-initialize […] the object that is the destination of the copy-initialization.
Wie von meinem Vorposter erwähnt, kannst du daher einen Move-Konstruktor deklarieren, denn schließlich wird die Temporary als rvalue übergeben. Ansonsten musst du wohl warten bis die libstdc++-Entwickler das Paper implementieren.
-
Wow, super, danke für die ausführliche Antwort