c++11 list initialization
-
hi,
ich weiß, dass die mit c++ eingeführte list initialization die mehrdeutigkeit in gewissen fällen auflöst:
T(); // funktionsaufruf oder temporary? T{}; // immer temporary T(U); // funktionsdeklaration oder temporary? T{U}; // immer temporarywas ich jedoch dubios finde, ist, dass folgender code nicht kompiliert:
#include <initializer_list> struct foo { foo(int a, double b) { } foo(std::initializer_list<int> l) { } }; int main() { foo bar{1, 2.}; }ich weiß, dass eine
initializer_listkein narrowing zulässt, aber wieso fällt der compiler dann nicht auf den anderen konstruktor zurück? kann ich im obigen beispiel auf irgendeine weise den ersten konstruktor aufrufen, ohne die alte schreibweise mit den klammern verwenden zu müssen?
und was ist allgemein die regel dahinter, wann welcher konstruktor bei list initialization auserwählt wird?
-
ahnungs loser schrieb:
die mit c++ eingeführte list initialization
*die mit c++11 eingeführte
ahnungs loser schrieb:
ohne die alte schreibweise mit den klammern verwenden zu müssen?
*mit den runden klammern ()
noch was das narrowing angeht: wieso geht dann wiederum das hier?
struct foo { foo(unsigned, unsigned) { } }; int main() { foo{1, 2}; }intzuunsignedist ja auch narrowing, hier beschwert sich der compiler aber nicht.
-
ahnungs loser schrieb:
hi,
ich weiß, dass die mit c++ eingeführte list initialization die mehrdeutigkeit in gewissen fällen auflöst:
ich weiß, dass eine
initializer_listkein narrowing zulässt, aber wieso fällt der compiler dann nicht auf den anderen konstruktor zurück?Weil narrowing erst eine Rolle spielt, nachdem der passende Konstruktor ausgewählt wurde. Die Konvertierungen sind ja möglich, nur eben verboten.
ahnungs loser schrieb:
kann ich im obigen beispiel auf irgendeine weise den ersten konstruktor aufrufen, ohne die alte schreibweise mit den klammern verwenden zu müssen?
nein.
ahnungs loser schrieb:
und was ist allgemein die regel dahinter, wann welcher konstruktor bei list initialization auserwählt wird?
Wenn initializer_list-Konstruktoren vorhanden sind und list-Initialisierung stattfindet, wird zuerst geschaut, ob bei Betrachtung nur dieser Konstruktoren die Überladung aufgelöst werden kann, und nur wenn das nicht der Fall ist, werden auch alle anderen Konstruktoren einbezogen.
Man könnte folgendes tun:
#include <initializer_list> #include <type_traits> struct foo { foo(int a, double b) { } template <typename T, typename std::enable_if<std::is_same<T, int>{}, int>::type = 0> foo(std::initializer_list<T> l) { } }; int main() { foo bar{1, 2.}; }das ist aber nicht sonderlich toll, weil der initializer_list-Konstruktor nun überhaupt keine Konvertierungen (nicht nur die narrowing Konvertierungen) mehr zulässt.
Das ist alles furchtbar-
Als Faustregel: Wenn neben einem initializer_list-Konstruktor noch andere Konstruktoren gebraucht werden, ist es zweckmäßig, zu prüfen, ob nicht besser ein Templatekonstruktor mit entsprechendem Parameterpack verwendet werden kann.int zu unsigned ist ja auch narrowing, hier beschwert sich der compiler aber nicht.
Weil die Initialisierer konstante Ausdrücke sind und ihre Werte in den Zieltyp passen.
-
danke für die erklärung, sehr einleuchtend. etwas noch: soll ich nun in meinem code durchgängig list initialization verwenden und nur in den sonderfällen (z.b. wenn ich einen vector<int> initialisiere) ausweichen oder soll ich generell bei der alten schreibweise bleiben und nur wechseln, wenn ich mir damit tipparbeit sparen kann?
ps: noch ein kleiner spezialfall hier:
#include <initializer_list> class nonaggregate { int a, b; public: nonaggregate(int a, int b) : a(a), b(b) { } }; nonaggregate f() { auto&& ret = { 1, 2 }; return ret; // kompiliert nicht return { 1, 2 }; // kompiliert } auto g() { return { 1, 2 }; // kompiliert nicht } int main() { }ich vermute, der ausdruck
{ 1, 2 }an sich hat keinen datentyp. laut [list=http://en.cppreference.com/w/cpp/utility/initializer_list]dieser quelle[/list] wird hingegen einestd::initializer_listerzeugt, wenn jener ausdruck an einautogebunden wird (so, wie in f). wieso aber trifft dies nicht auf g zu?
was mich ausserdem verwundert, ist, welche grammatikregel die return-anweisung in f erlaubt. hat diese art von kontext, sowie auch ein non-deduced parameter, dem man diesen typenlosen ausdruck ebenfalls übergeben darf, einen speziellen namen?
-
ahnungs loser schrieb:
ich vermute, der ausdruck
{ 1, 2 }an sich hat keinen datentyp.Es ist nicht einmal ein Ausdruck.
ahnungs loser schrieb:
laut [list=http://en.cppreference.com/w/cpp/utility/initializer_list]dieser quelle[/list] wird hingegen eine
std::initializer_listerzeugt, wenn jener ausdruck an einautogebunden wird (so, wie in f).richtig.
ahnungs loser schrieb:
wieso aber trifft dies nicht auf g zu?
weil der Standard es verbietet.
If the deduction is for a return statement and the initializer is a braced-init-list (8.5.4), the program is ill-formed.
ahnungs loser schrieb:
was mich ausserdem verwundert, ist, welche grammatikregel die return-anweisung in f erlaubt.
Diejenige, die besagt, dass auf ein return ein Ausdruck oder eine solche Initialisierungsliste folgen darf.