Obskure Überlegungen: exception safety std::less und andere basic relational operator wrappers
-
Als ich eines schönen Sommermorgens aufwachte, konnte ich eine Frage, die sich in meine Gedanken geschlichen hat, einfach nicht loswerden: "Wie ist das mit der exception guarantee bei std::less?". Nun, mir ist klar, dass das in der Praxis keine Rolle spielt, aber nebenbei hat es mich interessiert. Es ist klar, dass exceptions fliegen können, wenn der operator< wirft, aber was sagt der Standard ansonsten? Ich sehe jedenfalls nirgendwo Angaben, die garantieren, dass std::less(oder die anderen function objects aus demselben Abschnitt) nur werfen, wenn der genutzte operator wirft. Würde das nicht implizieren, dass selbst std::less<int> werfen kann?
Keine ernste Sache, ich bin nur neugierig. Ich kenne jedenfalls keinen Compiler bei dem std::less nicht als
template<typename T> struct less { bool operator ()(const T& l, const T& r) {return l < r;} };implementiert wäre.
-
Würde das nicht implizieren, dass selbst std::less<int> werfen kann?
Nein. Nur weil nicht jede minimale Funktion (und besonders jedes Funktionstemplate) noexcept-spezifiziert ist, heißt das nicht, dass sie auch theoretisch alle werfen können. Ein Großteil hat keine einzige Operation, die eine Exception werfen könnte.
-
Es ist erlaubt, eine weitere Spezialisierung zu less<> im namespace std hinzuzufügen. Die kann natürlich werfen, auch wenn operator< in dem Fall nicht werfen dürfte.
-
Schade, dass es da wirklich nichts gibt. Nunja, nichts womit man nicht leben kann, es gibt schließlich immer einen Weg.
-
Ja,
std::less<int>kann laut Standard werfen. Nur weil es jetzt werfen kann wird keiner wahnsinnig und geht auch davon aus. Außerdem ist es der Einfachkeit halber eben auch nicht spezifiziert.Vielleicht wird in Zukunft mal noch
noexcept(noexcept(lhs < rhs))o.ä. ergänzt. Dafür gibt es aber mMn. keinen Grund.
-
Ich glaube nicht, dass die besagten Funktionsobjekte selbst werfen dürfen.
n3337 schrieb:
17.6.5.12 Restrictions on exception handling [res.on.exception.handling]
1 Any of the functions defined in the C++ standard library can report a failure by throwing an exception of
a type described in its Throws: paragraph. An implementation may strengthen the exception-specification
for a non-virtual function by adding a non-throwing noexcept-specification.Eine entsprechender Throws-Abschnitt existiert für diese Objekte nicht.
Zulässig ist aber nat. dass diese Funktionen per Exception verlassen (exit) werden, offenbar dann, wenn der aufgerufene Operator wirft.
Kann aber sein, dass ich da zuviel hineininterpretiere, die Sprache für die Beschreibung der Standardbibliothek ist tendentiell weniger präzise.Für eine selbst gelieferte Spezialisierung sollte das Gleiche gelten
n3337 schrieb:
17.6.4.2.1 Namespace std [namespace.std]
1 The behavior of a C++ program is undefined if it adds declarations or definitions to namespace std or to a
namespace within namespace std unless otherwise specified. A program may add a template specialization
for any standard library template to namespace std only if the declaration depends on a user-defined type
and the specialization meets the standard library requirements for the original template and is not explicitly
prohibited.181
-
So wie ich den Standard interpretiere, bedeutet ein fehlender Throws-Abschnitt nicht notwendigerweise "nothrow".
n3337 §17.6.4.8 Other functions [res.on.functions] schrieb:
In certain cases (replacement functions, handler functions, operations on types used to instantiate standard library template components), the C++ standard library depends on components supplied by a C++ program. If these components do not meet their requirements, the Standard places no requirements on the implementation.
In particular, the effects are undefined in the following cases:
[...]
— for types used as template arguments when instantiating a template component, if the operations on the type do not implement the semantics of the applicable Requirements subclause (17.6.3.5, 23.2, 24.2, 26.2). Operations on such types can report a failure by throwing an exception unless otherwise specified.Soll heissen: Funktionen, die irgendwie von einem Template-Parameter abhängen können potentiell alles mögliche werfen.
So hat z.B. kein Container einen Trows-Abschnitt für push_back. Nicht einmal wird bad_alloc erwähnt, weil der übergebene Allocator das nicht zwingend werfen muss.
Meines Erachtens wäre folgendes absolut valide (weil std::less keine Requires-Clause hat):
template<> less<MyInt> { bool operator() { return rand()%2 ? throw "hey" : rand()%2; } };Was nicht erlaubt wäre, ist unter diesen Umständen std::less<MyInt> als Vergleichsfunktion für z.B. sort() zu verwenden (weil dafür das LessThanComparable-Konzept erfüllt sein müsste).
Andererseits darf
std::less<int>nicht werfen ("add a template specialization [...] only if the declaration depends on a user-defined type" aus campers Zitat). less<int> ist immer built-in operator< für ints.So ganz verstehe ich die Sache auch nicht "on such types" bedeutet wahrscheinlich "that depends directly or indirectly on such types" und "depends on a user-defined type" bedeutet wahrscheinlich "depends on a user-defined type outside of namespace std".
-
n3337 schrieb:
17.5.1.3 Requirements [structure.requirements]
/6
In some cases the semantic requirements are presented as C++ code. Such code is intended as a specifica-
tion of equivalence of a construct to another construct, not necessarily as the way the construct must be
implemented.16117.5.1.4 Detailed specifications [structure.specifications]
/4
Whenever the Effects: element specifies that the semantics of some function F are Equivalent to some code
sequence, then the various elements are interpreted as follows. If F’s semantics specifies a Requires: element,
then that requirement is logically imposed prior to the equivalent-to semantics. Next, the semantics of the
code sequence are determined by the Requires:, Effects:, Postconditions:, Returns:, Throws:, Complexity:,
Remarks:, Error conditions:, and Notes: specified for the function invocations contained in the code sequence.[
The value returned from F is specified by F’s Returns: element, or if F has no Returns: element, a non-void
return from F is specified by the Returns: elements in the code sequence. If F’s semantics contains a Throws:,
Postconditions:, or Complexity: element, then that supersedes any occurrences of that element in the code
sequence.20.8.5 Comparisons [comparisons]
/5
template <class T> struct less {
bool operator()(const T& x, const T& y) const;
typedef T first_argument_type;
typedef T second_argument_type;
typedef bool result_type;
};operator() returns x < y.
/8
For templates greater, less, greater_equal, and less_equal, the specializations for any pointer type
yield a total order, even if the built-in operators <, >, <=, >= do not.Die Spezifikation von less enthält kein Throws: Element, also ist - im Umkehrschluss - die Throws: Specifikation der angegeben Code-Sequenz anzuwenden, diese wiederum bestimmt sich nach den Throws-Spezifikation der in der Code-Sequenz enthaltenen Funktionsaufrufe.
-
camper schrieb:
Die Spezifikation von less enthält kein Throws: Element, also ist - im Umkehrschluss - die Throws: Specifikation der angegeben Code-Sequenz anzuwenden, diese wiederum bestimmt sich nach den Throws-Spezifikation der in der Code-Sequenz enthaltenen Funktionsaufrufe.
Das gilt aber nur für das Basistemplate std::less<T>. Die allgemeine Exception-Garantie von std::less lässt sich daraus nicht ableiten (siehe mein Post).
-
standonese schrieb:
camper schrieb:
Die Spezifikation von less enthält kein Throws: Element, also ist - im Umkehrschluss - die Throws: Specifikation der angegeben Code-Sequenz anzuwenden, diese wiederum bestimmt sich nach den Throws-Spezifikation der in der Code-Sequenz enthaltenen Funktionsaufrufe.
Das gilt aber nur für das Basistemplate std::less<T>. Die allgemeine Exception-Garantie von std::less lässt sich daraus nicht ableiten (siehe mein Post).
Mir ist nicht klar, inwiefern dein Zitat relevant ist. Wir sind uns einig, dass der Nutzer eine eigene Spezialisierung von std::less für eigene Typen schreiben darf. Und solange diese Spezialisierung nicht zusammen mit irgendeiner Komponente der Standardbibliothek verwendet wird, unterliegt sie logischerweise auch keinen Restriktionen, kann also u.a. alles Mögliche werfen. Das ist allerdings relativ uninteressant und kaum das, was der OP wissen wollte.
Was wenn die Spezialisierung zusammen mit der Standardbibliothek verwendet werden soll?
n3337 schrieb:
17.6.4.2.1 Namespace std [namespace.std]
/1
The behavior of a C++ program is undefined if it adds declarations or definitions to namespace std or to a
namespace within namespace std unless otherwise specified. A program may add a template specialization
for any standard library template to namespace std only if the declaration depends on a user-defined type
and the specialization meets the standard library requirements for the original template and is not explicitly
prohibited.181Dein Beispiel bewahrt nicht die Semantik der Standardspezifikation, indem std::less eben nicht den jeweiligen Operator < aufruft und dessen Ergebnis zurückgibt (es sei denn, dieser Operator macht genau das Gleiche).