Funktionsdeklaration
Eine Funktionsdeklaration führt den Funktionsnamen und ihren Typ ein. Eine Funktionsdefinition verknüpft den Funktionsnamen/Typ mit dem Funktionskörper.
Inhalt |
[bearbeiten] Funktionsdeklaration
Funktionsdeklarationen können in jedem Geltungsbereich erscheinen. Eine Funktionsdeklaration im Klassengeltungsbereich führt eine Klassen-Memberfunktion ein (es sei denn, der friend-Spezifizierer wird verwendet), siehe Memberfunktionen und Friendfunktionen für Details.
noptr-declarator ( parameter-list ) cv (optional) ref (optional) except (optional) attr (optional) |
(1) | ||||||||
noptr-declarator ( parameter-list ) cv (optional) ref (optional) except (optional) attr (optional)-> trailing |
(2) | (seit C++11) | |||||||
(siehe Deklarationen für die anderen Formen der declarator-Syntax)
| noptr-declarator | - | jeder gültige declarator, aber wenn er mit *, & oder && beginnt, muss er in Klammern eingeschlossen sein. | ||||||
| parameter-liste | - | eine möglicherweise leere, durch Kommas getrennte Liste der Funktionsparameter (siehe unten für Details) | ||||||
| attr | - | (seit C++11) eine Liste von Attributen. Diese Attribute werden auf den Typ der Funktion angewendet, nicht auf die Funktion selbst. Die Attribute für die Funktion erscheinen nach dem Bezeichner innerhalb des Deklarators und werden mit den Attributen kombiniert, die gegebenenfalls am Anfang der Deklaration erscheinen. | ||||||
| cv | - | const/volatile-Qualifizierung, nur in nicht-statischen Memberfunktionsdeklarationen erlaubt | ||||||
| ref | - | (seit C++11) Ref-Qualifizierung, nur in nicht-statischen Memberfunktionsdeklarationen erlaubt | ||||||
| except | - |
| ||||||
| trailing | - | Nachgestellter Rückgabetyp, nützlich, wenn der Rückgabetyp von Argumentnamen abhängt, wie z.B. template<class T, class U> auto add(T t, U u) -> decltype(t + u); oder kompliziert ist, wie z.B. in auto fpif(int)->int(*)(int) |
|
Wie in Deklarationen erwähnt, kann dem Deklarator eine requires-Klausel folgen, die die zugehörigen Einschränkungen für die Funktion deklariert, die erfüllt sein müssen, damit die Funktion durch Überladungsauflösung ausgewählt wird. (Beispiel: void f1(int a) requires true;) Beachten Sie, dass die zugehörige Einschränkung Teil der Funktionssignatur ist, aber nicht Teil des Funktionstyps. |
(seit C++20) |
Funktionsdeklaratoren können mit anderen Deklaratoren gemischt werden, wobei die Deklarationsspezifizierersequenz dies zulässt
// declares an int, an int*, a function, and a pointer to a function int a = 1, *p = NULL, f(), (*pf)(double); // decl-specifier-seq is int // declarator f() declares (but doesn't define) // a function taking no arguments and returning int struct S { virtual int f(char) const, g(int) &&; // declares two non-static member functions virtual int f(char), x; // compile-time error: virtual (in decl-specifier-seq) // is only allowed in declarations of non-static // member functions };
|
Die Verwendung eines volatile-qualifizierten Objekttyps als Parametertyp oder Rückgabetyp ist veraltet. |
(seit C++20) |
Der Rückgabetyp einer Funktion kann kein Funktionstyp oder Array-Typ sein (kann aber ein Zeiger oder eine Referenz darauf sein).
|
Wie bei jeder Deklaration gelten Attribute, die vor der Deklaration erscheinen, und Attribute, die unmittelbar nach dem Bezeichner innerhalb des Deklarators erscheinen, für die zu deklarierende oder definierende Entität (in diesem Fall die Funktion). [[noreturn]] void f [[noreturn]] (); // OK: both attributes apply to the function f Die nach dem Deklarator erscheinenden Attribute (in der obigen Syntax) gelten jedoch für den Typ der Funktion, nicht für die Funktion selbst. void f() [[noreturn]]; // Error: this attribute has no effect on the function itself |
(seit C++11) |
Rückgabetyp-DeduktionWenn die decl-specifier-seq der Funktionsdeklaration das Schlüsselwort auto enthält, kann der nachgestellte Rückgabetyp weggelassen werden und wird vom Compiler aus dem Typ des Ausdrucks abgeleitet, der in der return-Anweisung verwendet wird. Wenn der Rückgabetyp nicht decltype(auto) verwendet, folgt die Deduktion den Regeln der Template-Argument-Deduktion. int x = 1; auto f() { return x; } // return type is int const auto& f() { return x; } // return type is const int& Wenn der Rückgabetyp decltype(auto) ist, ist der Rückgabetyp so, wie er erhalten würde, wenn der in der return-Anweisung verwendete Ausdruck in int x = 1; decltype(auto) f() { return x; } // return type is int, same as decltype(x) decltype(auto) f() { return(x); } // return type is int&, same as decltype((x)) (Hinweis: „const decltype(auto)&“ ist ein Fehler, decltype(auto) muss allein verwendet werden) Wenn es mehrere return-Anweisungen gibt, müssen diese alle zum gleichen Typ deduziert werden. auto f(bool val) { if (val) return 123; // deduces return type int else return 3.14f; // Error: deduces return type float } Wenn es keine return-Anweisung gibt oder wenn das Argument der return-Anweisung ein void-Ausdruck ist, muss der deklarierte Rückgabetyp entweder decltype(auto) sein, in welchem Fall der deduzierte Rückgabetyp void ist, oder (möglicherweise cv-qualifiziert) auto, in welchem Fall der deduzierte Rückgabetyp dann (identisch cv-qualifiziert) void ist. auto f() {} // returns void auto g() { return f(); } // returns void auto* x() {} // Error: cannot deduce auto* from void Sobald eine return-Anweisung in einer Funktion gesehen wurde, kann der aus dieser Anweisung abgeleitete Rückgabetyp im Rest der Funktion verwendet werden, auch in anderen return-Anweisungen. auto sum(int i) { if (i == 1) return i; // sum’s return type is int else return sum(i - 1) + i; // OK: sum’s return type is already known } Wenn die return-Anweisung eine elementinitialisierte Liste verwendet, ist die Deduktion nicht erlaubt. auto func() { return {1, 2, 3}; } // Error Virtuelle Funktionen und Coroutinen(seit C++20) können keine Rückgabetyp-Deduktion verwenden. struct F { virtual auto f() { return 2; } // Error }; Funktionstemplates mit Ausnahme von Benutzerdefinierten Konvertierungsfunktionen können Rückgabetyp-Deduktion verwenden. Die Deduktion erfolgt bei der Instanziierung, auch wenn der Ausdruck in der return-Anweisung nicht abhängig ist. Diese Instanziierung befindet sich nicht in einem sofortigen Kontext für die Zwecke von SFINAE. template<class T> auto f(T t) { return t; } typedef decltype(f(1)) fint_t; // instantiates f<int> to deduce return type template<class T> auto f(T* t) { return *t; } void g() { int (*p)(int*) = &f; } // instantiates both fs to determine return types, // chooses second template overload Neudeklarationen oder Spezialisierungen von Funktionen oder Funktionstemplates, die Rückgabetyp-Deduktion verwenden, müssen die gleichen Platzhalter für den Rückgabetyp verwenden. auto f(int num) { return num; } // int f(int num); // Error: no placeholder return type // decltype(auto) f(int num); // Error: different placeholder template<typename T> auto g(T t) { return t; } template auto g(int); // OK: return type is int // template char g(char); // Error: not a specialization of the primary template g Ebenso dürfen Neudeklarationen oder Spezialisierungen von Funktionen oder Funktionstemplates, die keine Rückgabetyp-Deduktion verwenden, keinen Platzhalter verwenden. int f(int num); // auto f(int num) { return num; } // Error: not a redeclaration of f template<typename T> T g(T t) { return t; } template int g(int); // OK: specialize T as int // template auto g(char); // Error: not a specialization of the primary template g Explizite Instanziierungsdeklarationen instanziieren Funktionstemplates, die Rückgabetyp-Deduktion verwenden, nicht selbst. template<typename T> auto f(T t) { return t; } extern template auto f(int); // does not instantiate f<int> int (*p)(int) = f; // instantiates f<int> to determine its return type, // but an explicit instantiation definition // is still required somewhere in the program |
(seit C++14) |
[bearbeiten] Parameterliste
Die Parameterliste bestimmt die Argumente, die beim Aufruf der Funktion angegeben werden können. Es ist eine durch Kommas getrennte Liste von *Parameterdeklarationen*, die jeweils die folgende Syntax haben
| attr (optional) decl-specifier-seq declarator | (1) | ||||||||
|
attr (optional) |
(2) | (seit C++23) | |||||||
attr (optional) decl-specifier-seq declarator = initializer |
(3) | ||||||||
| attr (optional) decl-specifier-seq abstract-declarator (optional) | (4) | ||||||||
|
attr (optional) |
(5) | (seit C++23) | |||||||
attr (optional) decl-specifier-seq abstract-declarator (optional) = initializer |
(6) | ||||||||
void
|
(7) | ||||||||
| Falsche Verwendung | Beispiel |
|---|---|
| mehrere Parameter sind vorhanden | int f1(void, int); |
| der void-Parameter hat einen Namen | inf f2(void param); |
| void ist cv-qualifiziert | int f3(const void); |
| void ist abhängig | int f4(T); (wobei T void ist) |
| der void-Parameter ist ein expliziter Objektparameter (seit C++23) | int f5(this void); |
|
Obwohl decl-specifier-seq impliziert, dass es neben Typ-Spezifizierern noch andere Spezifizierer geben kann, ist der einzige andere zulässige Spezifizierer register sowie auto(bis C++11), und er hat keine Auswirkung. |
(bis C++17) |
|
Wenn einer der Funktionsparameter einen *Platzhalter* (entweder auto oder einen Konzepttyp) verwendet, ist die Funktionsdeklaration stattdessen eine abgekürzte Funktionstemplate-Deklaration. void f1(auto); // same as template<class T> void f1(T) void f2(C1 auto); // same as template<C1 T> void f2(T), if C1 is a concept |
(seit C++20) |
|
Eine Parameterdeklaration mit dem Spezifizierer this (Syntax (2)/(5)) deklariert einen *expliziten Objektparameter*. Ein expliziter Objektparameter kann keine Funktionsparameter-Packung sein und kann nur als erster Parameter der Parameterliste in den folgenden Deklarationen erscheinen:
Eine Memberfunktion mit einem expliziten Objektparameter hat die folgenden Einschränkungen:
struct C { void f(this C& self); // OK template<typename Self> void g(this Self&& self); // also OK for templates void p(this C) const; // Error: “const” not allowed here static void q(this C); // Error: “static” not allowed here void r(int, this C); // Error: an explicit object parameter // can only be the first parameter }; // void func(this C& self); // Error: non-member functions cannot have // an explicit object parameter |
(seit C++23) |
Parameternamen, die in Funktionsdeklarationen deklariert werden, dienen normalerweise nur der Selbstdokumentation. Sie werden (bleiben aber optional) in Funktionsdefinitionen verwendet.
Eine Mehrdeutigkeit entsteht in einer Parameterliste, wenn ein Typname in Klammern verschachtelt ist (einschließlich Lambda-Ausdrücken)(seit C++11). In diesem Fall besteht die Wahl zwischen der Deklaration eines Parameters vom Typ Zeiger auf Funktion und der Deklaration eines Parameters mit redundanten Klammern um den Bezeichner des declarator. Die Auflösung besteht darin, den Typnamen als einfachen Typspezifizierer (was der Zeigertyp auf Funktion ist) zu betrachten.
class C {}; void f(int(C)) {} // void f(int(*fp)(C param)) {} // NOT void f(int C) {} void g(int *(C[10])); // void g(int *(*fp)(C param[10])); // NOT void g(int *C[10]);
Der Parametertyp kann kein Typ sein, der eine Referenz oder einen Zeiger auf ein Array unbekannter Größe enthält, einschließlich mehrstufiger Zeiger/Arrays solcher Typen oder ein Zeiger auf Funktionen, deren Parameter solche Typen sind.
[bearbeiten] Verwendung einer Ellipse
Der letzte Parameter in der Parameterliste kann eine Ellipse (...) sein; dies deklariert eine variadische Funktion. Das Komma vor der Ellipse kann weggelassen werden(veraltet in C++26).
int printf(const char* fmt, ...); // a variadic function int printf(const char* fmt...); // same as above, but deprecated since C++26 template<typename... Args> void f(Args..., ...); // a variadic function template with a parameter pack template<typename... Args> void f(Args... ...); // same as above, but deprecated since C++26 template<typename... Args> void f(Args......); // same as above, but deprecated since C++26
[bearbeiten] Funktionstyp
[bearbeiten] Parameter-Typ-Liste
Die *Parameter-Typ-Liste* einer Funktion wird wie folgt bestimmt:
- Der Typ jedes Parameters (einschließlich Funktions-Parameter-Packs)(seit C++11) wird aus seiner eigenen Parameterdeklaration ermittelt.
- Nachdem der Typ jedes Parameters ermittelt wurde, werden alle Parameter vom Typ „Array von
T“ oder vom FunktionstypTzu „Zeiger aufT“ angepasst. - Nachdem die Liste der Parametertypen erstellt wurde, werden alle obersten cv-Qualifizierer, die einen Parametertyp modifizieren, beim Erstellen des Funktionstyps gelöscht.
- Die resultierende Liste der transformierten Parametertypen und die An- oder Abwesenheit der Ellipse oder einer Funktions-Parameter-Packung(seit C++11) ist die Parameter-Typ-Liste der Funktion.
void f(char*); // #1 void f(char[]) {} // defines #1 void f(const char*) {} // OK, another overload void f(char* const) {} // Error: redefines #1 void g(char(*)[2]); // #2 void g(char[3][2]) {} // defines #2 void g(char[3][3]) {} // OK, another overload void h(int x(const int)); // #3 void h(int (*)(int)) {} // defines #3
[bearbeiten] Bestimmung des Funktionstyps
In der Syntax (1), unter der Annahme, dass noptr-declarator eine eigenständige Deklaration ist, gegeben den Typ des qualified-id oder unqualified-id in noptr-declarator als „derived-declarator-type-list T“
|
(seit C++17) |
- Die(bis C++17)Andernfalls ist der(seit C++17) Typ der deklarierten Funktion
„derived-declarator-type-list Funktion von
parameter-type-list cv (optional) ref (optional)(seit C++11) zurückgebendT“.
|
In der Syntax (2), unter der Annahme, dass noptr-declarator eine eigenständige Deklaration ist, gegeben den Typ des qualified-id oder unqualified-id in noptr-declarator als „derived-declarator-type-list |
(seit C++11) |
|
(seit C++17) |
attr, falls vorhanden, gilt für den Funktionstyp. |
(seit C++11) |
// the type of “f1” is // “function of int returning void, with attribute noreturn” void f1(int a) [[noreturn]]; // the type of “f2” is // “constexpr noexcept function of pointer to int returning int” constexpr auto f2(int[] b) noexcept -> int; struct X { // the type of “f3” is // “function of no parameter const returning const int” const int f3() const; };
[bearbeiten] Nachgestellte Qualifizierer
Ein Funktionstyp mit cv oder ref (seit C++11) (einschließlich eines durch typedef benannten Typs) kann nur erscheinen als
- der Funktionstyp für eine nicht-statische Memberfunktion,
- der Funktionstyp, auf den ein Zeiger auf ein Mitglied verweist,
- der oberste Funktionstyp einer
typedef-Deklaration oder Aliasdeklaration(seit C++11), - das Typ-ID im Standardargument eines Typ-Templateparameters, oder
- das Typ-ID eines Template-Arguments für einen Typ-Templateparameter.
typedef int FIC(int) const; FIC f; // Error: does not declare a member function struct S { FIC f; // OK }; FIC S::*pm = &S::f; // OK
[bearbeiten] Funktionssignatur
Jede Funktion hat eine Signatur.
Die Signatur einer Funktion besteht aus ihrem Namen und der Parameter-Typ-Liste. Ihre Signatur enthält auch den umschließenden Namensraum, mit den folgenden Ausnahmen:
- Wenn die Funktion eine Memberfunktion ist, enthält ihre Signatur die Klasse, zu der die Funktion gehört, anstelle des umschließenden Namensraums. Ihre Signatur enthält außerdem die folgenden Komponenten, falls vorhanden:
- cv
|
(seit C++11) |
|
(seit C++20) |
except und attr(seit C++11) sind nicht Teil der Funktionssignatur, obwohl eine noexcept-Spezifikation den Funktionstyp beeinflusst(seit C++17).
[bearbeiten] Funktionsdefinition
Eine Nicht-Memberfunktionsdefinition kann nur im Namensraum-Geltungsbereich erscheinen (es gibt keine verschachtelten Funktionen). Eine Memberfunktionsdefinition kann auch im Körper einer Klassendefinition erscheinen. Sie haben die folgende Syntax:
| attr (optional) decl-specifier-seq (optional) declarator virt-specs (optional) contract-specs (optional) function-body |
(1) | ||||||||
| attr (optional) decl-specifier-seq (optional) declarator requires-clause contract-specs (optional) function-body |
(2) | (seit C++20) | |||||||
| attr | - | (seit C++11) eine Liste von Attributen. Diese Attribute werden mit den Attributen nach dem Bezeichner im declarator (siehe oben auf dieser Seite) kombiniert, falls vorhanden. |
| decl-specifier-seq | - | der Rückgabetyp mit Spezifizierern, wie in der Grammatik für Deklarationen |
| Deklarator | - | Funktionsdeklarator, wie in der obigen Grammatik für Funktionsdeklarationen (kann in Klammern gesetzt werden) |
| virt-specs | - | (seit C++11) override, final oder deren Kombination in beliebiger Reihenfolge |
| requires-clause | - | eine requires-Klausel |
| contract-specs | - | (seit C++26) eine Liste von Funktionsvertragspezifizierern |
| function-body | - | der Funktionskörper (siehe unten) |
function-body ist eine der folgenden:
| ctor-initializer (optional) compound-statement | (1) | ||||||||
| function-try-block | (2) | ||||||||
= default ; |
(3) | (seit C++11) | |||||||
= delete ; |
(4) | (seit C++11) | |||||||
= delete ( string-literal ); |
(5) | (seit C++26) | |||||||
| ctor-initializer | - | Member-Initialisierungsliste, nur in Konstruktoren erlaubt |
| compound-statement | - | der in geschweifte Klammern eingeschlossene Satz von Anweisungen, der den Körper einer Funktion bildet |
| function-try-block | - | ein Funktions-try-Block |
| string-literal | - | ein nicht ausgewerteter String-Literal, der verwendet werden könnte, um die Begründung für die Löschung der Funktion zu erklären |
int max(int a, int b, int c) { int m = (a > b) ? a : b; return (m > c) ? m : c; } // decl-specifier-seq is “int” // declarator is “max(int a, int b, int c)” // body is { ... }
Der Funktionskörper ist eine zusammengesetzte Anweisung (eine Sequenz von null oder mehr Anweisungen, umschlossen von einem Paar geschweifter Klammern), die ausgeführt wird, wenn der Funktionsaufruf erfolgt. Darüber hinaus umfasst der Funktionskörper eines Konstruktors auch Folgendes:
- Für alle nicht-statischen Datenmember, deren Bezeichner in der Member-Initialisierungsliste des Konstruktors fehlen, die Standard-Member-Initialisierer oder(seit C++11) Standard-Initialisierungen, die zur Initialisierung der entsprechenden Member-Subobjekte verwendet werden.
- Für alle Basisklassen, deren Typnamen in der Member-Initialisierungsliste des Konstruktors fehlen, die Standard-Initialisierungen, die zur Initialisierung der entsprechenden Basisklassen-Subobjekte verwendet werden.
|
Wenn eine Funktionsdefinition einen virt-specs enthält, muss sie eine Memberfunktion definieren. |
(seit C++11) |
|
Wenn eine Funktionsdefinition eine requires-clause enthält, muss sie eine Template-Funktion definieren. |
(seit C++20) |
void f() override {} // Error: not a member function void g() requires (sizeof(int) == 4) {} // Error: not a templated function
Die Parametertypen sowie der Rückgabetyp einer Funktionsdefinition können keine (möglicherweise cv-qualifizierten) unvollständigen Klassentypen sein, es sei denn, die Funktion ist als gelöscht definiert(seit C++11). Die Prüfung auf Vollständigkeit erfolgt nur im Funktionskörper, was es Memberfunktionen erlaubt, die Klasse, in der sie definiert sind (oder ihre umschließende Klasse), zurückzugeben, auch wenn diese zum Zeitpunkt der Definition unvollständig ist (sie ist im Funktionskörper vollständig).
Die im declarator einer Funktionsdefinition deklarierten Parameter sind im Geltungsbereich innerhalb des Körpers. Wenn ein Parameter im Funktionskörper nicht verwendet wird, muss er nicht benannt werden (ein abstrakter Deklarator reicht aus).
void print(int a, int) // second parameter is not used { std::printf("a = %d\n", a); }
Obwohl Top-Level cv-Qualifizierer bei den Parametern in Funktionsdeklarationen verworfen werden, modifizieren sie den Typ des Parameters, wie er im Körper einer Funktion sichtbar ist.
void f(const int n) // declares function of type void(int) { // but in the body, the type of “n” is const int }
Standardmäßig übernommene FunktionenWenn die Funktionsdefinition die Syntax (3) hat, wird die Funktion als *explizit standardmäßig übernommen* definiert. Eine explizit standardmäßig übernommene Funktion muss eine spezielle Memberfunktion oder eine Vergleichsoperatorfunktion(seit C++20) sein, und sie darf keine Standardargumente haben. Eine explizit standardmäßig übernommene spezielle Memberfunktion `F1` darf sich von der entsprechenden speziellen Memberfunktion `F2` unterscheiden, die implizit deklariert worden wäre, wie folgt:
Wenn sich der Typ von `F1` vom Typ von `F2` in einer Weise unterscheidet, die nicht von den vorhergehenden Regeln erlaubt ist, dann:
Eine Funktion, die bei ihrer ersten Deklaration explizit standardmäßig übernommen wird, ist implizit inline und ist implizit constexpr, wenn sie eine constexpr-Funktion sein kann. struct S { S(int a = 0) = default; // error: default argument void operator=(const S&) = default; // error: non-matching return type ~S() noexcept(false) = default; // OK, different exception specification private: int i; S(S&); // OK, private copy constructor }; S::S(S&) = default; // OK, defines copy constructor Explizit standardmäßig übernommene Funktionen und implizit deklarierte Funktionen werden zusammen als *standardmäßig übernommene* Funktionen bezeichnet. Ihre tatsächlichen Definitionen werden implizit bereitgestellt, siehe die entsprechenden Seiten für Details. Gelöschte FunktionenWenn die Funktionsdefinition die Syntax (4) oder (5)(seit C++26) hat, wird die Funktion als *explizit gelöscht* definiert. Jede Verwendung einer gelöschten Funktion ist ill-formed (das Programm wird nicht kompiliert). Dies umfasst Aufrufe, sowohl explizite (mit einem Funktionsaufrufoperator) als auch implizite (Aufruf eines gelöschten überladenen Operators, einer speziellen Memberfunktion, einer Allokationsfunktion usw.), die Erstellung eines Zeigers oder Zeiger-auf-Member auf eine gelöschte Funktion, und sogar die Verwendung einer gelöschten Funktion in einem Ausdruck, der nicht potenziell ausgewertet wird. Eine nicht-reine virtuelle Memberfunktion kann als gelöscht definiert werden, obwohl sie implizit ODR-verwendet wird. Eine nicht-gelöschte Funktion kann nur von nicht-gelöschten Funktionen überschrieben werden, und eine gelöschte Funktion kann nur von gelöschten Funktionen überschrieben werden.
Wenn die Funktion überladen ist, erfolgt zuerst die Überladungsauflösung, und das Programm ist nur dann ill-formed, wenn die gelöschte Funktion ausgewählt wurde. struct T { void* operator new(std::size_t) = delete; void* operator new[](std::size_t) = delete("new[] is deleted"); // since C++26 }; T* p = new T; // Error: attempts to call deleted T::operator new T* p = new T[5]; // Error: attempts to call deleted T::operator new[], // emits a diagnostic message “new[] is deleted” Die gelöschte Definition einer Funktion muss die erste Deklaration in einer Translationseinheit sein: eine zuvor deklarierte Funktion kann nicht als gelöscht neu deklariert werden. struct T { T(); }; T::T() = delete; // Error: must be deleted on the first declaration Vom Benutzer bereitgestellte FunktionenEine Funktion ist *vom Benutzer bereitgestellt*, wenn sie vom Benutzer deklariert und bei ihrer ersten Deklaration weder explizit standardmäßig übernommen noch gelöscht wurde. Eine vom Benutzer bereitgestellte, explizit standardmäßig übernommene Funktion (d. h. nach ihrer ersten Deklaration explizit standardmäßig übernommen) wird an dem Punkt definiert, an dem sie explizit standardmäßig übernommen wird; wenn eine solche Funktion implizit als gelöscht definiert wird, ist das Programm ill-formed. Das Deklarieren einer Funktion nach ihrer ersten Deklaration als standardmäßig übernommen kann eine effiziente Ausführung und eine prägnante Definition ermöglichen, während gleichzeitig eine stabile binäre Schnittstelle zu einer sich entwickelnden Codebasis ermöglicht wird. // All special member functions of “trivial” are // defaulted on their first declarations respectively, // they are not user-provided struct trivial { trivial() = default; trivial(const trivial&) = default; trivial(trivial&&) = default; trivial& operator=(const trivial&) = default; trivial& operator=(trivial&&) = default; ~trivial() = default; }; struct nontrivial { nontrivial(); // first declaration }; // not defaulted on the first declaration, // it is user-provided and is defined here nontrivial::nontrivial() = default; AmbiguitätsauflösungIm Falle einer Mehrdeutigkeit zwischen einem Funktionskörper und einem Initialisierer, der mit `{` oder `=`(seit C++26) beginnt, wird die Mehrdeutigkeit durch Überprüfung des Typs des Deklarator-Bezeichners des noptr-declarator behoben.
using T = void(); // function type using U = int; // non-function type T a{}; // defines a function doing nothing U b{}; // value-initializes an int object T c = delete("hello"); // defines a function as deleted U d = delete("hello"); // copy-initializes an int object with // the result of a delete expression (ill-formed)
__func__Innerhalb des Funktionskörpers ist die vordefinierte lokale Variable __func__ definiert, als ob durch: static const char __func__[] = "function-name"; Diese Variable hat Blockgültigkeitsbereich und statische Speicherklasse. struct S { S(): s(__func__) {} // OK: initializer-list is part of function body const char* s; }; void f(const char* s = __func__); // Error: parameter-list is part of declarator Führen Sie diesen Code aus Mögliche Ausgabe Foo Bar Pub ~Bar |
(seit C++11) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
FunktionsvertragspezifiziererFunktionsdeklarationen und Lambda-Ausdrücke können eine Sequenz von *Funktionsvertragspezifizierern* enthalten, wobei jeder Spezifizierer die folgende Syntax hat:
1) Leitet eine *Vorbedingungsassertion* ein.
2,3) Leitet eine *Nachbedingungsassertion* ein.
2) Die Assertion bindet sich nicht an das Ergebnis.
3) Die Assertion bindet sich an das Ergebnis.
Eine Funktionsvertragsassertion ist eine Vertragsassertion, die mit einer Funktion verknüpft ist. Das Prädikat einer Funktionsvertragsassertion wird contextually converted (kontextuell konvertiert) zu bool. Die folgenden Funktionen können nicht mit Funktionsvertragspezifizierern deklariert werden:
VorbedingungsassertionenEine Vorbedingungsassertion ist mit dem Eintritt in eine Funktion verknüpft. int divide(int dividend, int divisor) pre(divisor != 0) { return dividend / divisor; } double square_root(double num) pre(num >= 0) { return std::sqrt(num); } NachbedingungsassertionenEine Nachbedingungsassertion ist mit dem normalen Verlassen einer Funktion verknüpft. Wenn eine Nachbedingungsassertion einen identifier hat, führt der Funktionsvertragspezifizierer identifier als Namen einer *Ergebnisbindung* der zugehörigen Funktion ein. Eine Ergebnisbindung bezeichnet das Objekt oder die Referenz, die durch den Aufruf dieser Funktion zurückgegeben wird. Der Typ einer Ergebnisbindung ist der Rückgabetyp ihrer zugehörigen Funktion. int absolute_value(int num) post(r : r >= 0) { return std::abs(num); } double sine(double num) post(r : r >= -1.0 && r <= 1.0) { if (std::isnan(num) || std::isinf(num)) // exiting via an exception never causes contract violation throw std::invalid_argument("Invalid argument"); return std::sin(num); } Wenn eine Nachbedingungsassertion einen identifier hat und der Rückgabetyp der zugehörigen Funktion (möglicherweise cv-qualifiziert) void ist, ist das Programm ill-formed. void f() post(r : r > 0); // Error: no value can be bound to “r” Wenn der deklarierte Rückgabetyp einer nicht-templated Funktion einen Platzhaltertyp enthält, kann eine Nachbedingungsassertion mit einem identifier nur in einer Funktionsdefinition erscheinen. auto g(auto&) post(r : r >= 0); // OK, “g” is a template auto h() post(r : r >= 0); // Error: cannot name the return value auto k() post(r : r >= 0) // OK, “k” is a definition { return 0; } VertragskonsistenzEine *Redeklaration* `D` einer Funktion oder Funktionstemplate `func` muss entweder keine contract-specs haben oder die gleichen contract-specs wie jede erste Deklaration `F` haben, die von `D` erreichbar ist. Wenn `D` und `F` in verschiedenen Translationseinheiten liegen, ist eine Diagnose nur erforderlich, wenn `D` an ein benanntes Modul angehängt ist. Wenn eine Deklaration `F1` eine erste Deklaration von `func` in einer Translationseinheit ist und eine Deklaration `F2` eine erste Deklaration von `func` in einer anderen Translationseinheit ist, müssen `F1` und `F2` die gleichen contract-specs angeben, keine Diagnose erforderlich. Zwei contract-specs sind gleich, wenn sie aus denselben Funktionsvertragspezifizierern in derselben Reihenfolge bestehen. Ein Funktionsvertragspezifizierer `C1` in einer Funktionsdeklaration `D1` ist gleich einem Funktionsvertragspezifizierer `C2` in einer Funktionsdeklaration `D2`, wenn alle folgenden Bedingungen erfüllt sind:
Wenn diese Bedingung nicht allein aufgrund des Vergleichs zweier Lambda-Ausdrücke erfüllt ist, die innerhalb der predicates enthalten sind, ist keine Diagnose erforderlich. bool b1, b2; void f() pre (b1) pre([]{ return b2; }()); void f(); // OK, function contract specifiers omitted void f() pre (b1) pre([]{ return b2; }()); // Error: closures have different types void f() pre (b1); // Error: function contract specifiers are different int g() post(r : b1); int g() post(b1); // Error: no result binding namespace N { void h() pre (b1); bool b1; void h() pre (b1); // Error: function contract specifiers differ // according to the one−definition rule } |
(seit C++26) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
[bearbeiten] Hinweise
Im Falle einer Mehrdeutigkeit zwischen einer Variablendeklaration mit der direkten Initialisierungssyntax und einer Funktionsdeklaration wählt der Compiler immer die Funktionsdeklaration; siehe direkte Initialisierung.
| Feature-Testmakro | Wert | Std | Feature |
|---|---|---|---|
__cpp_decltype_auto |
201304L |
(C++14) | decltype(auto)
|
__cpp_return_type_deduction |
201304L |
(C++14) | Rückgabetypableitung für normale Funktionen |
__cpp_explicit_this_parameter |
202110L |
(C++23) | explizite Objektparameter (ableitendes this) |
__cpp_deleted_function |
202403L |
(C++26) | gelöschte Funktion mit Begründung |
[bearbeiten] Schlüsselwörter
[bearbeiten] Beispiel
#include <iostream> #include <string> // simple function with a default argument, returning nothing void f0(const std::string& arg = "world!") { std::cout << "Hello, " << arg << '\n'; } // the declaration is in namespace (file) scope // (the definition is provided later) int f1(); // function returning a pointer to f0, pre-C++11 style void (*fp03())(const std::string&) { return f0; } // function returning a pointer to f0, with C++11 trailing return type auto fp11() -> void(*)(const std::string&) { return f0; } int main() { f0(); fp03()("test!"); fp11()("again!"); int f2(std::string) noexcept; // declaration in function scope std::cout << "f2(\"bad\"): " << f2("bad") << '\n'; std::cout << "f2(\"42\"): " << f2("42") << '\n'; } // simple non-member function returning int int f1() { return 007; } // function with an exception specification and a function try block int f2(std::string str) noexcept try { return std::stoi(str); } catch (const std::exception& e) { std::cerr << "stoi() failed!\n"; return 0; } // deleted function, an attempt to call it results in a compilation error void bar() = delete # if __cpp_deleted_function ("reason") # endif ;
Mögliche Ausgabe
stoi() failed!
Hello, world!
Hello, test!
Hello, again!
f2("bad"): 0
f2("42"): 42[bearbeiten] Fehlerberichte
Die folgenden Verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
| DR | angewendet auf | Verhalten wie veröffentlicht | Korrigiertes Verhalten |
|---|---|---|---|
| CWG 135 | C++98 | Memberfunktionen, die in einer Klasse definiert sind konnten keinen Parameter vom Typ oder Rückgabetyp haben ihre eigene Klasse, da diese unvollständig ist |
erlaubt |
| CWG 332 | C++98 | ein Parameter könnte den cv-qualifizierten Typ void haben | verboten |
| CWG 393 | C++98 | Typen, die Zeiger/Referenzen auf enthalten Array unbekannter Größe konnten keine Parameter sein |
solche Typen sind erlaubt |
| CWG 452 | C++98 | Die Member-Initialisierungsliste war kein Teil des Funktionskörpers | sie ist |
| CWG 577 | C++98 | abhängiger Typ void konnte verwendet werden, um eine Funktion ohne Parameter zu deklarieren |
nur nicht-abhängige void ist erlaubt |
| CWG 1327 | C++11 | Standardmäßig übernommene oder gelöschte Funktionen konnten nicht mit override oder final angegeben werden |
erlaubt |
| CWG 1355 | C++11 | nur spezielle Memberfunktionen konnten vom Benutzer bereitgestellt werden | auf alle Funktionen erweitert |
| CWG 1394 | C++11 | gelöschte Funktionen konnten keinen Parameter von einem unvollständigen Typ haben oder einen unvollständigen Typ zurückgeben |
unvollständiger Typ erlaubt |
| CWG 1824 | C++98 | Die Vollständigkeitsprüfung des Parametertyps und des Rückgabetyps einer Funktionsdefinition konnte gemacht werden außerhalb des Kontexts der Funktionsdefinition |
nur im Kontext der Funktionsdefinition |
| CWG 1877 | C++14 | Rückgabetypableitung behandelte return; als return void(); | leitete einfach den Rückgabe- ab Typ als void in diesem Fall |
| CWG 2015 | C++11 | Die implizite ODR-Verwendung einer gelöschten virtuellen Funktion war ill-formed |
solche ODR-Verwendungen sind ausgenommen von der Verwendungsbeschränkung |
| CWG 2044 | C++14 | Rückgabetypableitung bei Funktionen, die void zurückgeben würde fehlschlagen, wenn der deklarierte Rückgabetyp decltype(auto) ist |
aktualisierte die Ableitung Regel, um diesen Fall zu behandeln |
| CWG 2081 | C++14 | Funktions-Redeklarationen konnten den Rückgabetyp verwenden Ableitung, auch wenn die erste Deklaration dies nicht tut |
nicht erlaubt |
| CWG 2144 | C++11 | {} konnte ein Funktionskörper oder ein Initialisierer am selben Ort sein | unterschieden durch den Typ des Deklarator-Bezeichners |
| CWG 2145 | C++98 | Der declarator in einer Funktionsdefinition konnte nicht in Klammern gesetzt werden | erlaubt |
| CWG 2259 | C++11 | Die Ambiguitätsauflösungsregel bezüglich geklammerter Typnamen deckte Lambda-Ausdrücke nicht ab |
abgedeckt |
| CWG 2430 | C++98 | Bei der Definition einer Memberfunktion in einer Klassendefinition konnte der Typ dieser Klasse nicht der Rückgabetyp oder Parametertyp sein, aufgrund der Auflösung von CWG-Problem 1824 |
nur im Funktionskörper |
| CWG 2760 | C++98 | Der Funktionskörper eines Konstruktors enthielt nicht die Initialisierungen nicht im regulären Funktionskörper des Konstruktors angegeben |
beinhaltet auch diese Initialisierungen |
| CWG 2831 | C++20 | eine Funktionsdefinition mit einer requires-clause konnte eine nicht-templated Funktion definieren |
verboten |
| CWG 2846 | C++23 | explizite Objekt-Memberfunktionen konnten keine Out-of-Class-Definitionen haben | erlaubt |
| CWG 2915 | C++23 | anonyme explizite Objektparameter konnten den Typ void haben | verboten |
[bearbeiten] Siehe auch
| C-Dokumentation für Funktionen deklarieren
|