Namensräume
Varianten
Aktionen

Funktionsdeklaration

Von cppreference.com
< cpp‎ | Sprache
 
 
C++ Sprache
Allgemeine Themen
Kontrollfluss
Bedingte Ausführungsaussagen
if
Iterationsanweisungen (Schleifen)
for
Bereichs-for (C++11)
Sprunganweisungen
Funktionen
Funktionsdeklaration
Lambda-Funktionsausdruck
inline-Spezifizierer
Dynamische Ausnahmespezifikationen (bis C++17*)
noexcept-Spezifizierer (C++11)
Ausnahmen
Namensräume
Typen
Spezifizierer
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Speicherdauer-Spezifizierer
Initialisierung
Ausdrücke
Alternative Darstellungen
Literale
Boolesch - Ganzzahl - Gleitkommazahl
Zeichen - String - nullptr (C++11)
Benutzerdefinierte (C++11)
Dienstprogramme
Attribute (C++11)
Typen
typedef-Deklaration
Typalias-Deklaration (C++11)
Umwandlungen
Speicherzuweisung
Klassen
Klassenspezifische Funktionseigenschaften
explicit (C++11)
static

Spezielle Member-Funktionen
Templates
Sonstiges
 
 

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)

1) Reguläre Syntax für Funktionsdeklaratoren.
2) Deklaration des nachgestellten Rückgabetyps. Die decl-specifier-seq muss in diesem Fall das Schlüsselwort auto enthalten.
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 -

dynamische Ausnahme-Spezifikationen

(bis C++11)

entweder dynamische Ausnahmespezifikation
oder noexcept-Spezifikation

(seit C++11)
(bis C++17)

noexcept-Spezifikation

(seit C++17)
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-Deduktion

Wenn 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 decltype eingekapselt würde.

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) this decl-specifier-seq declarator

(2) (seit C++23)
attr (optional) decl-specifier-seq declarator = initializer (3)
attr (optional) decl-specifier-seq abstract-declarator (optional) (4)

attr (optional) this decl-specifier-seq abstract-declarator (optional)

(5) (seit C++23)
attr (optional) decl-specifier-seq abstract-declarator (optional) = initializer (6)
void (7)
1) Deklariert einen benannten (formalen) Parameter. Für die Bedeutung von decl-specifier-seq und declarator siehe Deklarationen.
int f(int a, int* p, int (*(*x)(double))[3]);
2) Deklariert einen benannten expliziten Objektparameter.
3) Deklariert einen benannten (formalen) Parameter mit einem Standardwert.
int f(int a = 7, int* p = nullptr, int (*(*x)(double))[3] = nullptr);
4) Deklariert einen unbenannten Parameter.
int f(int, int*, int (*(*)(double))[3]);
5) Deklariert einen unbenannten expliziten Objektparameter.
6) Deklariert einen unbenannten Parameter mit einem Standardwert.
int f(int = 7, int* = nullptr, int (*(*)(double))[3] = nullptr);
7) Zeigt an, dass die Funktion keine Parameter nimmt, sie ist das exakte Synonym für eine leere Parameterliste: int f(void); und int f(); deklarieren dieselbe Funktion.
void ist die einzige Syntax, die einer leeren Parameterliste entspricht, andere Verwendungen von void-Parametern sind ill-formed.
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:

  • Die Funktion ist nicht static.
  • Die Funktion ist nicht virtuell.
  • Der Deklarator der Funktion enthält kein cv und ref.
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:

  1. Der Typ jedes Parameters (einschließlich Funktions-Parameter-Packs)(seit C++11) wird aus seiner eigenen Parameterdeklaration ermittelt.
  2. Nachdem der Typ jedes Parameters ermittelt wurde, werden alle Parameter vom Typ „Array von T“ oder vom Funktionstyp T zu „Zeiger auf T“ angepasst.
  3. Nachdem die Liste der Parametertypen erstellt wurde, werden alle obersten cv-Qualifizierer, die einen Parametertyp modifizieren, beim Erstellen des Funktionstyps gelöscht.
  4. 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

  • Wenn die Ausnahmespezifikation nicht wirft, ist der Typ der deklarierten Funktion
    „derived-declarator-type-list noexcept Funktion von
    parameter-type-list cv (optional) ref  (optional) zurückgebend 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ückgebend T“.

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 T“ (T muss in diesem Fall auto sein)

(seit C++11)
  • Wenn die Ausnahmespezifikation nicht wirft, ist der Typ der deklarierten Funktion
    „derived-declarator-type-list noexcept Funktion von
    parameter-type-list cv (optional) ref  (optional) zurückgebend trailing “.
(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) zurückgebend trailing “.

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

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
  • ref
(seit C++11)
  • nachgestellte requires-Klausel
  • Wenn die Funktion eine Nicht-Template Friendfunktion mit einer nachgestellten requires-Klausel ist, enthält ihre Signatur die umschließende Klasse anstelle des umschließenden Namensraums. Die Signatur enthält auch die nachgestellte requires-Klausel.
(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)
1) Eine Funktionsdefinition ohne Einschränkungen.
2) Eine Funktionsdefinition mit Einschränkungen.
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)
1) Regulärer Funktionskörper.
3) Explizit standardmäßig definierte Funktionsdefinition.
4) Explizit gelöschte Funktionsdefinition.
5) Explizit gelöschte Funktionsdefinition mit Fehlermeldung.
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 Funktionen

Wenn 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:

  • `F1` und `F2` dürfen unterschiedliche ref und/oder except haben.
  • Wenn `F2` einen nicht-objektbezogenen Parameter vom Typ const C& hat, darf der entsprechende nicht-objektbezogene Parameter von `F1` vom Typ `C&` sein.
  • Wenn `F2` einen impliziten Objektparameter vom Typ "Referenz auf `C`" hat, kann `F1` eine explizite Objektmemberfunktion sein, deren expliziter Objektparameter vom (möglicherweise unterschiedlichen) Typ "Referenz auf `C`" ist, in diesem Fall würde sich der Typ von `F1` vom Typ von `F2` darin unterscheiden, dass der Typ von `F1` einen zusätzlichen Parameter hat.
(seit C++23)

Wenn sich der Typ von `F1` vom Typ von `F2` in einer Weise unterscheidet, die nicht von den vorhergehenden Regeln erlaubt ist, dann:

  • Wenn `F1` ein Zuweisungsoperator ist und sich der Rückgabetyp von `F1` vom Rückgabetyp von `F2` unterscheidet oder der nicht-objektbezogene Parametertyp von `F1` keine Referenz ist, ist das Programm ill-formed.
  • Andernfalls, wenn `F1` bei seiner ersten Deklaration explizit standardmäßig übernommen wird, wird sie als gelöscht definiert.
  • Andernfalls ist das Programm fehlerhaft.

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 Funktionen

Wenn 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 ein string-literal vorhanden ist, wird die Implementierung ermutigt, den Text als Teil der resultierenden Diagnosemeldung aufzunehmen, die die Begründung für die Löschung anzeigt oder eine Alternative vorschlägt.

(seit C++26)

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 Funktionen

Eine 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ösung

Im 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.

  • Wenn der Typ ein Funktionstyp ist, wird die mehrdeutige Tokensequenz als Funktionskörper behandelt.
  • Andernfalls wird die mehrdeutige Tokensequenz als Initialisierer behandelt.
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
#include <iostream>
 
void Foo() { std::cout << __func__ << ' '; }
 
struct Bar
{
    Bar() { std::cout << __func__ << ' '; }
    ~Bar() { std::cout << __func__ << ' '; }
    struct Pub { Pub() { std::cout << __func__ << ' '; } };
};
 
int main()
{
    Foo();
    Bar bar;
    Bar::Pub pub;
}

Mögliche Ausgabe

Foo Bar Pub ~Bar
(seit C++11)

Funktionsvertragspezifizierer

Funktionsdeklarationen und Lambda-Ausdrücke können eine Sequenz von *Funktionsvertragspezifizierern* enthalten, wobei jeder Spezifizierer die folgende Syntax hat:

pre attr (optional) ( predicate ) (1)
post attr (optional) ( predicate ) (2)
post attr (optional) ( identifier result-attr (optional) : predicate ) (3)
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.
attr - eine Liste von Attributen, die zur eingeführten Vertragsassertion gehören
predicate - beliebiger Ausdruck (außer nicht geklammerte Komma-Ausdrücke)
identifier - der Bezeichner, der sich auf das Ergebnis bezieht
result-attr - eine Liste von Attributen, die zur Ergebnisbindung gehören


Vorbedingungsassertion und Nachbedingungsassertion werden zusammen als *Funktionsvertragsassertionen* bezeichnet.

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:

Vorbedingungsassertionen

Eine 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);
}

Nachbedingungsassertionen

Eine 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;
}

Vertragskonsistenz

Eine *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:

  • Die predicates von `C1` und `C2` würden die Ein-Definition-Regel erfüllen, wenn sie in Funktionsdefinitionen auf den Deklarationen `D1` und `D2` platziert würden (wenn `D1` und `D2` in verschiedenen Translationseinheiten liegen, verhalten sich entsprechende Entitäten, die innerhalb jedes predicate definiert sind, so, als ob es eine einzelne Entität mit einer einzigen Definition gäbe), mit Ausnahme der folgenden Umbenennungen:
    • Die Umbenennung der Parameter der deklarierten Funktion.
    • Die Umbenennung von Template-Parametern einer die deklarierte Funktion umschließenden Template.
    • Die Umbenennung der Ergebnisbindung (falls vorhanden).
  • Sowohl `C1` als auch `C2` haben einen identifier oder keinen.

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

default, delete, pre, post

[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