Rückgabewerte als Parameter nutzen
-
Hallo zusammen...
Folgendes produziert einen Fehler:
struct a { int j; }; // eine struct
a fkt1(int q) { return a { q }; } // eine Funktion, die eine struct zurückgibt
void fkt2(a* ptr) { ... } // eine Funktion, die einen struct a-Pointer als Parameter will
// hier kommt der Fehler
fkt2(&fkt1(12)); // das Problem ist, dass das hinter dem & kein "l-Wert" ist... gut... (wenn ich beim Visual C++ 2019 den "Konformitätsmodus" aktiviere)also bauen wir das mal um
void fkt2b(a& ref) { ... } // eine Funktion, die eine struct a-Referenz als Parameter will
// das hier klappt
fkt2b(fkt1(12)); // ... Visual C++ 2019 frisst das... ähm. Kann mir das einer erklären? Warum ist das eine ok und das andere nicht? Ist das nicht im Grunde das gleiche?
... was, wenn ich der struct einen "operator a*" hinzufüge? Dann könnte ich den Aufruf fkt2(fkt1(12)); auch durchbringen. ... ist das jetzt ok oder ein Fehler vom Visual C++ 2019? ... was sind die Begründungen??
Rudolf
-
@meiru sagte in Rückgabewerte als Parameter nutzen:
void fkt2b(a& ref) { ... } // eine Funktion, die eine struct a-Referenz als Parameter will
// das hier klappt
fkt2b(fkt1(12)); // ... Visual C++ 2019 frisst dasWenn du da wirklich
a& ref
und nichta const& ref
stehen hast, dann darf das laut Standard nicht funktionieren. Und Visual Studio 2019 frisst das auch nur, wenn man eben nicht den "Konformitätsmodus" (/permissive-
) aktiviert hat.Mit
a const& ref
dagegen ist es erlaubt.... ähm. Kann mir das einer erklären? Warum ist das eine ok und das andere nicht? Ist das nicht im Grunde das gleiche?
&RValue
ist verboten.
T&
an RValue binden ist auch verboten.
T const&
an RValue binden ist erlaubt.Warum? Weil der Standard das sagt. Warum sagt der Standard das? Meine Vermutung: Weil es hilft Fehler zu vermeiden.
- In idiomatischen C++ Programmen wird man nur sehr selten die Adresse eines temporären Objekts benötigen. Klar, es gibt manche Fälle wo das Sinn machen würde. Aber viel wahrscheinlicher ist dass jmd. die Sprache nicht verstanden bzw. nicht aufgepasst hat, und gerade dabei ist sich in den Fuss zu schiessen.
- Wenn eine Funktion einen
T&
Parameter hat (stattT const&
), dann will sie meist das Objekt ändern. Ein temporäres Objekt zu ändern macht aber kaum Sinn. Das Objekt ist nach dem Aufruf der Funktion ja nicht mehr ansprechbar und wird auch bald sterben - die Änderung wird also nie jmd. sehen. Auch hier ist die Wahrscheinlichkeit gross dass jemand der versucht so eine Funktion mit einem temporären Objekt aufzurufen gerade dabei ist sich selbst in den Fuss zu schiessen. - Eine Funktion die einen
T const&
Parameter hat will sich aber normalerweise nur den Wert des Objekts ansehen, es aber nicht ändern. Und sich den Wert eines temporären Objekts anzusehen ist natürlich völlig valide.
... was, wenn ich der struct einen "operator a*" hinzufüge? Dann könnte ich den Aufruf fkt2(fkt1(12)); auch durchbringen.
Ja, könntest du.
... ist das jetzt ok oder ein Fehler vom Visual C++ 2019?
Nein, das wäre dann standardkonform.
... was sind die Begründungen??
Naja, wenn du selbst extra einen
operator a*
definierst, dann willst du ja offensichtlich dass du dir damit in den Fuss schiessen kannst. Da hält dich C++ dann auch nicht davon ab.Bzw. wenn du einen
operator a*
haben willst der nur mit LValues funktioniert, dann kannst du ihn ja einfach LValue-qualifizieren. Alsostruct a { operator a* () & { return this; } };
-
@hustbaer sagte in Rückgabewerte als Parameter nutzen:
Wenn du da wirklich
a& ref
und nichta const& ref
stehen hast, dann darf das laut Standard nicht funktionieren. Und Visual Studio 2019 frisst das auch nur, wenn man eben nicht den "Konformitätsmodus" (/permissive-
) aktiviert hat.Ach, richtig... ich hab versucht einen nicht const R-value per & einer Funktion zu übergeben, die const * nimmt... das geht nicht... wohl weil es zwischenzeitlich ungültig wäre...
Mit
a const& ref
dagegen ist es erlaubt.&RValue
ist verboten.
T&
an RValue binden ist auch verboten.
T const&
an RValue binden ist erlaubt.und &const RValue? um es einer "fkt3(const a*)" Funktion zu übergeben? ... sollte ok sein, oder?
Warum? Weil der Standard das sagt. Warum sagt der Standard das? Meine Vermutung: Weil es hilft Fehler zu vermeiden.
hmm... kann man jetzt sehen wie man will... eine Variable ändern, die danach gleich wieder abgebaut wird... da weiss ich jetzt nicht, warum das den Standard stören sollte? ...
... was, wenn ich der struct einen "operator a*" hinzufüge? Dann könnte ich den Aufruf fkt2(fkt1(12)); auch durchbringen.
Ja, könntest du.
... ist das jetzt ok oder ein Fehler vom Visual C++ 2019?
Nein, das wäre dann standardkonform.
... was sind die Begründungen??
Naja, wenn du selbst extra einen
operator a*
definierst, dann willst du ja offensichtlich dass du dir damit in den Fuss schiessen kannst. Da hält dich C++ dann auch nicht davon ab.Bzw. wenn du einen
operator a*
haben willst der nur mit LValues funktioniert, dann kannst du ihn ja einfach LValue-qualifizieren. Alsostruct a { operator a* () & { return this; } };
ok, alles klar... mal sehen wie ich's löse... aber, hab jetzt die ganze Idee hinter dem "Verbot" verstanden... danke für die tolle Antwort
-
@meiru sagte in Rückgabewerte als Parameter nutzen:
Ach, richtig... ich hab versucht einen nicht const R-value per & einer Funktion zu übergeben, die const * nimmt... das geht nicht... wohl weil es zwischenzeitlich ungültig wäre...
Nein.
&value
erfordert immer ne LValue. Obconst
oder nicht, eine RValue ist da nicht erlaubt.hmm... kann man jetzt sehen wie man will... eine Variable ändern, die danach gleich wieder abgebaut wird... da weiss ich jetzt nicht, warum das den Standard stören sollte? ...
Wie ich schon geschrieben habe: weil es darauf hindeutet dass man vermutlich einen Fehler gemacht hat.
Beispiel:int make_upper(std::string& str) { // make str upper return number_of_characters_changed; } void some_fn() { std::cout << "Foo: " << make_upper(get_foo()) << "\n"; }
Hier wurde vermutlich
make_upper
falsch verwendet, man wollte statt dessen wohl eher den Inhalt des temporären Strings nach dem Aufruf vonmake_upper
ausgeben. Und daher ist es verboten.Dagegen:
std::string to_upper(std::string const& str) { std::string copy = str; // make copy upper return copy; } void some_fn() { std::cout << "Foo: " << to_upper(get_foo()) << "\n"; }
Hier ist wohl alles OK. Und daher ist es erlaubt.
ok, alles klar... mal sehen wie ich's löse... aber, hab jetzt die ganze Idee hinter dem "Verbot" verstanden... danke für die tolle Antwort
Was willst du denn konkret lösen? Vielleicht gibt's da ja eine gute übliche Lösung.
-
@hustbaer sagte in Rückgabewerte als Parameter nutzen:
Was willst du denn konkret lösen? Vielleicht gibt's da ja eine gute übliche Lösung.
Nun... ich habe hier eine Schnittstelle, die nimmt als Parameter 1 struct oder ein struct array (kleine structs mit 1 Pointer, 1 int drin oder so). Vom Design her weiss ich, dass damals die Frage war, ob man das so bauen soll
void fkt(const struct* p); // einzeln
void fkt(const struct* p, int length); // arrayoder so
void fkt(const struct& r); // einzeln
void fkt(const struct* p, int length); // array... und die Entscheidung war damals, das mit dem Pointer zu machen. (weil der Aufruf oft auch von Stellen kommt, bei dem man schon ein Pointer hat und der irgendwo mitten in einen Speicherblock zeigt)
Jetzt muss ich aber in meinem Programm oft diese Funktion füttern mit einem Wert, den ich von einer anderen Funktion bekomme (die Funktion steckt in meinem Programm).
struct buildit(...);
und die Lösung war früher (als Visual C++ das nicht interessiert hat)
fkt(&buildit(...));
jetzt kracht's neuerdings aber... und ... die Frage ist jetzt, was man wie ändern soll, damit das wieder sauber läuft und keine zweite Zeile braucht. ... irgendwie würde ich da gerne die Helper und Helper-Helper so bauen, dass das alles einfach im Hintergrund abläuft... daher überlege ich mir echt gerade, ob ich dieses buildit einen Helper zurückgeben lassen soll mit einem operator* auf eine const Basis-struct?
struct helper : public original-struct
{
operator const original-struct*();
};keine Ahnung, was da "sauber" wäre...
-
@meiru sagte in Rückgabewerte als Parameter nutzen:
Oder du gibst ihm halt 'nen Namen...
const auto s = buildit(...); fkt(&s);
-
Wenn's da wirklich nur um eine bzw. ein paar wenige Funktionen geht, dann würde ich für diese eine Funktionen kleine Wrapper bauen:
inline void fktWrapper(const T& t) { fkt(&t); }
Wenn du willst kannst du sogar Overloads im selben Namespace wie
fkt
machen, dann müsste man den aufrufenden Code garnichtkaum ändern.Mit Konvertierungs-Operatoren bin ich sehr vorsichtig - die verwende ich wirklich sehr selten. Weil da einfach so viel Unsinn damit passieren kann. Das alles könnte man mit so einem Operator dann schreiben:
if (buildit()) { } auto x1 = buildit() + 42; auto x2 = buildit() != nullptr; auto x3 = buildit() != 0; a const* p1 = buildit(); void const* p2 = buildit();
Was halt alles Quatsch ist.
Eine andere Möglichkeit wäre der Klasse eine "benannte" Hilfsfunktion zu verpassen:
struct a { a const* address() const { return this; } }; //... fkt(buildit().address());
-
ps: Was auch noch ginge, wäre der Wrapper-Klasse einen
operator &
zu verpassen:struct a { a const* operator &() const { return this; } };
Dann könnte der aufrufende Code so bleiben wie er ist, also einfach
fkt(&buildit());
Finde ich auch nicht 100% sauber, aber immer noch besser als einen impliziten Konvertierungsoperator zu
a const*
.
-
@hustbaer sagte in Rückgabewerte als Parameter nutzen:
ps: Was auch noch ginge, wäre der Wrapper-Klasse einen
operator &
zu verpassen:Finde ich auch nicht 100% sauber, aber immer noch besser als einen impliziten Konvertierungsoperator zu
a const*
.Also die Idee gefällt mir am besten... wirklich gut! ... ich habe jetzt das gewählt. Danke für deinen Einsatz