Namespaces
Variants

Function declaration

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

関数宣言は関数名とその型を導入します。関数定義は関数名/型と関数本体を関連付けます。

目次

関数宣言

関数宣言は任意のスコープに現れることができます。クラススコープでの関数宣言は( friend 指定子が使用されない限り)クラスメンバ関数を導入します。詳細については メンバ関数 および フレンド関数 を参照してください。

noptr-declarator ( parameter-list ) cv  (オプション) ref   (オプション) except  (オプション) attr  (オプション) (1)
noptr-declarator ( parameter-list ) cv  (オプション) ref   (オプション) except  (オプション) attr  (オプション)
-> trailing
(2) (C++11以降)

(詳細は Declarations を参照。他の形式の declarator 構文については)

1) 通常の関数宣言構文。
2) 末尾戻り値型宣言。この場合の decl-specifier-seq はキーワード auto を含まなければならない。
noptr-declarator - 任意の有効な declarator ですが、 * & 、または && で始まる場合は括弧で囲む必要があります。
parameter-list - 空である可能性のある、カンマ区切りの関数パラメータのリスト(詳細は下記参照)
attr - (C++11以降) 属性 のリスト。これらの属性は関数自体ではなく、関数の型に適用されます。関数の属性は、識別子の後ろの宣言子内に現れ、宣言の先頭に現れる属性(もしあれば)と結合されます。
cv - const/volatile修飾。非静的メンバ関数宣言でのみ許可されます
ref - (C++11以降) 参照修飾。非静的メンバ関数宣言でのみ許可されます
except -

動的例外指定

(C++11まで)

動的例外指定 または noexcept指定

(C++11以降)
(C++17まで)

noexcept指定

(C++17以降)
trailing - 後置戻り値型。戻り値の型が引数名に依存する場合(例: template < class T, class U > auto add ( T t, U u ) - > decltype ( t + u ) ; )や複雑な場合(例: auto fpif ( int ) - > int ( * ) ( int ) )に有用です


宣言 で述べたように、宣言子の後には requires を続けることができ、これは関数の関連する 制約 を宣言します。この制約は、 オーバーロード解決 によって関数が選択されるために満たされなければなりません。(例: void f1 ( int a ) requires true ; )関連する制約は関数のシグネチャの一部ですが、関数型の一部ではないことに注意してください。

(C++20以降)

関数宣言子は、他の宣言子と混在させることができます。ただし、 宣言指定子シーケンス が許可する場合に限ります:

// int、int*、関数、関数ポインタを宣言
int a = 1, *p = NULL, f(), (*pf)(double);
// decl-specifier-seq は int
// 宣言子 f() は引数を取らずintを返す関数を宣言(定義ではない)
struct S
{
    virtual int f(char) const, g(int) &&; // 2つの非静的メンバ関数を宣言
    virtual int f(char), x; // コンパイルエラー: virtual(decl-specifier-seq内)は
                            // 非静的メンバ関数の宣言でのみ許可される
};

volatile修飾されたオブジェクト型をパラメータ型または戻り値の型として使用することは非推奨です。

(since C++20)

関数の戻り値の型は、関数型または配列型にすることはできません(ただし、それらへのポインタまたは参照は可能です)。

あらゆる宣言と同様に、宣言の前に現れる属性と、宣言子内の識別子の直後に現れる属性は両方とも、宣言または定義されているエンティティ(この場合は関数)に適用されます:

[[noreturn]] void f [[noreturn]] (); // OK: 両方の属性が関数fに適用される

しかしながら、宣言子の後に現れる属性(上記の構文では)は、関数自体ではなく関数の型に適用されます:

void f() [[noreturn]]; // エラー: この属性は関数自体に効果を持たない
(C++11以降)

戻り値型の推論

関数宣言の decl-specifier-seq にキーワード auto が含まれている場合、後置戻り値型は省略可能であり、コンパイラによって 非破棄 return 文で使用されるオペランドの型から推論されます。戻り値型が decltype ( auto ) を使用しない場合、推論は テンプレート引数推論 の規則に従います:

int x = 1;
auto f() { return x; }        // 戻り値の型は int
const auto& f() { return x; } // 戻り値の型は const int&

戻り値の型が decltype ( auto ) の場合、戻り値の型はreturn文で使用されたオペランドが decltype で囲まれた場合に得られる型と同じになります:

int x = 1;
decltype(auto) f() { return x; }  // 戻り値の型はint、decltype(x)と同じ
decltype(auto) f() { return(x); } // 戻り値の型はint&、decltype((x))と同じ

(注:「 const decltype ( auto ) & 」は誤りです。 decltype ( auto ) は単独で使用する必要があります)

複数のreturn文がある場合、それらはすべて同じ型に推論されなければなりません:

auto f(bool val)
{
    if (val) return 123; // 戻り値の型をintと推論
    else return 3.14f;   // エラー: 戻り値の型をfloatと推論
}

return文がない場合、またはreturn文の被演算子がvoid式(被演算子のないreturn文を含む)である場合、宣言された戻り値の型は decltype ( auto ) でなければならず、この場合の推定される戻り値の型は void であるか、あるいは(possibly cv-qualified) auto でなければならず、この場合の推定される戻り値の型は(同一のcv修飾を持つ) void となる:

auto f() {}              // voidを返す
auto g() { return f(); } // voidを返す
auto* x() {}             // エラー: voidからauto*を推論できません

関数内でreturn文が一度見つかると、その文から推測された戻り値の型が関数の残りの部分、他のreturn文を含めて使用されるようになります:

auto sum(int i)
{
    if (i == 1)
        return i;              // sumの戻り値型はint
    else
        return sum(i - 1) + i; // OK: sumの戻り値型は既に判明している
}

戻り値の文が 波括弧で囲まれた初期化子リスト を使用する場合、推論は許可されません:

auto func() { return {1, 2, 3}; } // エラー

仮想関数 および コルーチン (C++20以降) は戻り値型の推論を使用できません:

struct F
{
    virtual auto f() { return 2; } // エラー
};

関数テンプレート のうち、 ユーザー定義変換関数 以外は戻り値型の推論を使用できます。この推論は、return文内の式が 依存名 でない場合でも、インスタンス化時に発生します。このインスタンス化は、 SFINAE の目的において即時コンテキストには含まれません。

template<class T>
auto f(T t) { return t; }
typedef decltype(f(1)) fint_t;    // f<int>をインスタンス化して戻り値の型を推論
template<class T>
auto f(T* t) { return *t; }
void g() { int (*p)(int*) = &f; } // 両方のfをインスタンス化して戻り値の型を決定し、
                                  // 2番目のテンプレートオーバーロードを選択

戻り値の型推論を使用する関数または関数テンプレートの再宣言または特殊化は、同じ戻り値のプレースホルダーを使用する必要があります:

auto f(int num) { return num; }
// int f(int num);            // エラー: プレースホルダー戻り値型なし
// decltype(auto) f(int num); // エラー: 異なるプレースホルダー
template<typename T>
auto g(T t) { return t; }
template auto g(int);     // OK: 戻り値型はint
// template char g(char); // エラー: プライマリテンプレートgの特殊化ではない

同様に、戻り値型の推論を使用しない関数または関数テンプレートの再宣言または特殊化は、プレースホルダーを使用してはなりません:

int f(int num);
// auto f(int num) { return num; } // エラー: fの再宣言ではない
template<typename T>
T g(T t) { return t; }
template int g(int);      // OK: Tをintとして特殊化
// template auto g(char); // エラー: プライマリテンプレートgの特殊化ではない

明示的インスタンス化宣言 は、戻り値型推論を使用する関数テンプレート自体をインスタンス化しません:

template<typename T>
auto f(T t) { return t; }
extern template auto f(int); // f<int>のインスタンス化は行わない
int (*p)(int) = f; // 戻り値の型を決定するためにf<int>をインスタンス化するが、
                   // 明示的なインスタンス化定義はプログラム内のどこかで
                   // 依然として必要である
(C++14以降)

パラメータリスト

パラメータリストは、関数が呼び出された際に指定できる引数を決定します。これはカンマ区切りの parameter declarations のリストであり、それぞれ以下の構文を持ちます:

attr  (オプション) decl-specifier-seq declarator (1)

attr  (オプション) this decl-specifier-seq declarator

(2) (C++23以降)
attr  (オプション) decl-specifier-seq declarator = initializer (3)
attr  (オプション) decl-specifier-seq abstract-declarator  (オプション) (4)

attr  (オプション) this decl-specifier-seq abstract-declarator  (オプション)

(5) (C++23以降)
attr  (オプション) decl-specifier-seq abstract-declarator  (オプション) = initializer (6)
void (7)
1) 名前付き(仮)パラメータを宣言します。 decl-specifier-seq declarator の意味については、 declarations を参照してください。
int f ( int a, int * p, int ( * ( * x ) ( double ) ) [ 3 ] ) ;
2) 名前付きの explicit object parameter を宣言します。
3) 名前付き(仮)パラメータを default value で宣言します。
int f ( int a = 7 , int * p = nullptr, int ( * ( * x ) ( double ) ) [ 3 ] = nullptr ) ;
4) 無名パラメータを宣言します。
int f ( int , int * , int ( * ( * ) ( double ) ) [ 3 ] ) ;
5) 無名の explicit object parameter を宣言します。
6) デフォルト値を持つ無名パラメータを宣言します。 default value
int f ( int = 7 , int * = nullptr, int ( * ( * ) ( double ) ) [ 3 ] = nullptr ) ;
7) 関数がパラメータを取らないことを示し、空のパラメータリストと完全に同義です: int f ( void ) ; int f ( ) ; は同じ関数を宣言します。
void は空のパラメータリストと等価な唯一の構文であり、その他の void パラメータの使用法は不適格です:
誤った使用法
複数のパラメータが存在する場合 int f1 ( void , int ) ;
void パラメータが名前付けされている場合 inf f2 ( void param ) ;
void がCV修飾されている場合 int f3 ( const void ) ;
void 依存名 である場合 int f4 ( T ) ; (ただし T void の場合)
void パラメータが 明示的オブジェクトパラメータ である場合 (C++23以降) int f5 ( this void ) ;

decl-specifier-seq は型指定子以外の specifiers が存在しうることを示唆しているが、実際に許可されている他の指定子は register および auto (C++11まで) のみであり、効果はない。

(C++17まで)

関数パラメータのいずれかが プレースホルダー ( auto または concept type )を使用する場合、その関数宣言は 省略形関数テンプレート 宣言となる:

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
(C++20以降)

指定子 this を持つパラメータ宣言(構文 ( 2 ) / ( 5 ) )は 明示的オブジェクトパラメータ を宣言する。

明示的オブジェクトパラメータは function parameter pack にはなれず、以下の宣言のパラメータリストの最初のパラメータとしてのみ現れることができる:

明示的オブジェクトパラメータを持つmember functionには以下の制限がある:

  • 関数は static ではない。
  • 関数は virtual ではない。
  • 関数の宣言子には cv 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
(C++23以降)

関数宣言で宣言されたパラメータ名は、通常は自己文書化の目的のみに使用されます。それらは関数定義で使用されますが(任意のままです)。

パラメータリストにおいて、型名が括弧内にネストされている場合に曖昧性が生じます lambda expressions を含む) (C++11以降) 。この場合、関数ポインタ型のパラメータ宣言と、 declarator の識別子を冗長な括弧で囲んだパラメータ宣言の間で選択が生じます。解決策は、その型名を simple type specifier (関数ポインタ型)として扱うことです:

class C {};
void f(int(C)) {} // void f(int(*fp)(C param)) {}
                  // これは void f(int C) {} ではない
void g(int *(C[10])); // void g(int *(*fp)(C param[10]));
                      // これは void g(int *C[10]); ではない

パラメータの型は、未知の境界を持つ配列への参照またはポインタを含む型(そのような型の多段階ポインタ/配列を含む)、またはパラメータがそのような型である関数へのポインタを含むことはできません。

省略記号の使用

パラメータリストの最後のパラメータは省略記号( ... )にすることができます。これは 可変引数関数 を宣言します。省略記号の前のカンマは省略可能です (C++26で非推奨)

int printf(const char* fmt, ...); // 可変引数関数
int printf(const char* fmt...);   // 上記と同じだが、C++26以降非推奨
template<typename... Args>
void f(Args..., ...); // パラメータパックを持つ可変引数関数テンプレート
template<typename... Args>
void f(Args... ...);  // 上記と同じだが、C++26以降非推奨
template<typename... Args>
void f(Args......);   // 上記と同じだが、C++26以降非推奨

関数型

パラメータ型リスト

関数の parameter-type-list は以下のように決定されます:

  1. 各パラメータの型 (関数 parameter packs を含む) (since C++11) は、それぞれの parameter declaration から決定されます。
  2. 各パラメータの型を決定した後、「 T の配列」型または関数型 T のパラメータは「 T へのポインタ」に調整されます。
  3. パラメータ型のリストを生成した後、関数型を形成する際にパラメータ型を修飾するトップレベルの cv-qualifiers は削除されます。
  4. 変換されたパラメータ型の結果リストと、 ellipsis または関数 parameter pack (since C++11) の有無が、関数のparameter-type-listとなります。
void f(char*);         // #1
void f(char[]) {}      // #1を定義
void f(const char*) {} // OK、別のオーバーロード
void f(char* const) {} // エラー: #1を再定義
void g(char(*)[2]);   // #2
void g(char[3][2]) {} // #2を定義
void g(char[3][3]) {} // OK、別のオーバーロード
void h(int x(const int)); // #3
void h(int (*)(int)) {}   // #3を定義

関数型の決定

構文 (1) において、 noptr-declarator を独立した宣言と仮定し、 noptr-declarator 内の qualified-id または unqualified-id の型が「derived-declarator-type-list T 」で与えられる場合:

  • 例外指定が 非スロー の場合、宣言される関数の型は
    「派生宣言子型リスト noexcept 関数で、
    パラメータ型リスト cv  (オプション) ref   (オプション) を引数に取り T を返す型」となる。
(C++17以降)
  • それ以外の場合、 (C++17まで) それ以外の場合、 (C++17以降) 宣言された関数の型は
    「derived-declarator-type-list function of
    parameter-type-list cv  (オプション) ref   (オプション) (C++11以降) returning T 」となる。

構文 (2) において、 noptr-declarator を独立した宣言と仮定し、 noptr-declarator 内の qualified-id または unqualified-id の型が「derived-declarator-type-list T 」である場合(この場合 T auto でなければならない):

(C++11以降)
  • 例外指定が non-throwing の場合、宣言される関数の型は
    「derived-declarator-type-list noexcept function of
    parameter-type-list cv  (オプション) ref   (オプション) returning trailing 」となる。
(C++17以降)
  • それ以外の場合、 (C++17以前) それ以外の場合、 (C++17以降) 宣言される関数の型は
    「derived-declarator-type-list function of
    parameter-type-list cv  (オプション) ref   (オプション) returning trailing 」となる。

attr が存在する場合、それは関数型に適用される。

(C++11以降)
// 「f1」の型は
// 「intを引数に取りvoidを返す関数、noreturn属性付き」
void f1(int a) [[noreturn]];
// 「f2」の型は
// 「constexpr noexcept関数、intへのポインタを引数に取りintを返す」
constexpr auto f2(int[] b) noexcept -> int;
struct X
{
    // 「f3」の型は
    // 「引数なしconst関数、const intを返す」
    const int f3() const;
};

トレーリング修飾子

関数型に cv  または ref   (C++11以降) 修飾子( typedef 名で指定される型を含む)が付いたものは、以下の場合にのみ現れます:

  • 非静的メンバ関数の関数型、
  • メンバポインタが参照する関数型、
  • 関数のtypedef宣言 typedef のトップレベル関数型 または エイリアス宣言 (C++11以降)
  • テンプレート型パラメータのデフォルト引数における type-id 、または
  • テンプレート型パラメータに対するテンプレート引数のtype-id。
typedef int FIC(int) const;
FIC f;     // エラー: メンバー関数を宣言していない
struct S
{
    FIC f; // OK
};
FIC S::*pm = &S::f; // OK

関数シグネチャ

すべての関数にはシグネチャがあります。

関数のシグネチャは、その名前と parameter-type-list から構成されます。そのシグネチャには、以下の例外を除いて、外側の namespace も含まれます:

  • 関数が メンバ関数 である場合、そのシグネチャには、囲んでいる名前空間の代わりに、その関数がメンバであるクラスが含まれます。また、存在する場合は以下の構成要素もシグネチャに含まれます:
  • cv
  • ref
(C++11以降)
  • 末尾 requires
  • 関数が非テンプレートの friend 関数で末尾 requires 節を持つ場合、そのシグネチャは外側の名前空間ではなく外側のクラスを含みます。シグネチャには末尾 requires 節も含まれます。
(C++20以降)

except および attr (C++11以降) は関数シグネチャに関与しない 、ただし noexcept 指定 は関数型に影響する (C++17以降)

関数定義

非メンバ関数の定義は名前空間スコープでのみ記述可能です(ネストした関数は存在しません)。 member function の定義は class definition の本体内部でも記述できます。これらは以下の構文を持ちます:

attr  (オプション) decl-specifier-seq  (オプション) declarator
virt-specs  (オプション) contract-specs  (オプション) function-body
(1)
attr  (オプション) decl-specifier-seq  (オプション) declarator
requires-clause contract-specs  (オプション) function-body
(2) (C++20以降)
1) 制約のない関数定義。
2) 制約付きの関数定義。
attr - (C++11以降) 属性 のリスト。これらの属性は、識別子の後の declarator 内の属性(このページの上部を参照)と結合されます(もしあれば)。
decl-specifier-seq - 宣言文法 と同様の、指定子付きの戻り値型
declarator - 関数宣言子。上記の関数宣言文法と同じ(括弧で囲むことが可能)
virt-specs - (C++11以降) override final 、またはそれらの任意の順序での組み合わせ
requires-clause - requires
contract-specs - (C++26以降) 関数契約指定子 のリスト
function-body - 関数本体(下記参照)


function-body は以下のいずれかです:

コンストラクタ初期化子  (オプション) 複合文 (1)
関数tryブロック (2)
= default ; (3) (C++11以降)
= delete ; (4) (C++11以降)
= delete ( 文字列リテラル ); (5) (C++26以降)
1) 通常の関数本体。
3) 明示的にデフォルト化された関数定義。
4) 明示的に削除された関数定義。
5) エラーメッセージ付きの明示的に削除された関数定義。
ctor-initializer - メンバ初期化子リスト 、コンストラクタでのみ許可される
compound-statement - 関数の本体を構成する中括弧で囲まれた 文のシーケンス
function-try-block - 関数の try ブロック
string-literal - 関数が削除された理由を説明するために使用できる 未評価文字列リテラル
int max(int a, int b, int c)
{
    int m = (a > b) ? a : b;
    return (m > c) ? m : c;
}
// decl-specifier-seq は「int」
// declarator は「max(int a, int b, int c)」
// body は { ... }

関数本体は compound statement (一対の波括弧で囲まれたゼロ個以上の文のシーケンス)であり、関数呼び出しが行われたときに実行されます。さらに、 constructor の関数本体には以下も含まれます:

関数定義が virt-specs を含む場合、それは メンバー関数 を定義しなければならない。

(C++11以降)

関数定義が requires-clause を含む場合、それは テンプレート化された関数 を定義しなければならない。

(C++20以降)
void f() override {} // エラー: メンバー関数ではありません
void g() requires (sizeof(int) == 4) {} // エラー: テンプレート関数ではありません

関数定義のパラメータ型および戻り値の型は、(cv修飾されている可能性のある) incomplete class types であってはならない (C++11以降) 。ただし、関数がdeletedとして定義されている場合は除く。完全性チェックは関数本体でのみ行われるため、 member functions は、定義時点では不完全であっても(関数本体では完全である)、自身が定義されているクラス(またはその外側のクラス)を返すことができる。

関数定義の declarator で宣言されたパラメータは、本体内部で in scope となります。関数本体でパラメータが使用されない場合、名前を付ける必要はありません(抽象宣言子を使用すれば十分です):

void print(int a, int) // 2番目のパラメータは使用されない
{
    std::printf("a = %d\n", a);
}

仮引数のトップレベルの cv修飾子 は関数宣言では無視されますが、関数本体内部で見えるパラメータの型を変更します:

void f(const int n) // void(int)型の関数を宣言
{
    // ただし、本体内部では「n」の型はconst int
}

デフォルト化関数

関数定義が構文 ( 3 ) の場合、その関数は 明示的にデフォルト化された ものとして定義されます。

明示的にデフォルト化された関数は、 特殊メンバ関数 または 比較演算子関数 (C++20以降) でなければならず、 デフォルト引数 を持ってはならない。

明示的にデフォルト化された特殊メンバ関数 F1 は、暗黙的に宣言された対応する特殊メンバ関数 F2 と以下の点で異なることが許可されています:

  • F1 F2 は異なる ref および/または except を持つ可能性があります。
  • F2 が型 const C & の非オブジェクトパラメータを持つ場合、 F1 の対応する非オブジェクトパラメータは型 C& である可能性があります。
  • F2 が「 C への参照」型の暗黙のオブジェクトパラメータを持つ場合、 F1 明示的オブジェクトパラメータ が(異なる可能性のある)「 C への参照」型である明示的オブジェクトメンバ関数であってもよく、 この場合、 F1 の型は F2 の型とは、 F1 の型が追加のパラメータを持つという点で異なることになる。
(C++23以降)

F1 の型が F2 の型と、前述の規則で許可されている以外の方法で異なる場合:

  • F1 が代入演算子であり、かつ F1 の戻り値の型が F2 の戻り値の型と異なるか、または F1 の非オブジェクトパラメータ型が参照ではない場合、プログラムは不適格です。
  • それ以外の場合、 F1 が最初の宣言で明示的にdefault指定されている場合、それはdeletedとして定義されます。
  • それ以外の場合、プログラムは不適格です。

最初の宣言で明示的にデフォルト化された関数は、暗黙的に inline となり、また constexpr function として可能な場合には暗黙的にconstexprとなります。

struct S
{
    S(int a = 0) = default;             // エラー: デフォルト引数
    void operator=(const S&) = default; // エラー: 戻り値の型が一致しない
    ~S() noexcept(false) = default;     // OK、例外仕様が異なる
private:
    int i;
    S(S&);          // OK、プライベートなコピーコンストラクタ
};
S::S(S&) = default; // OK、コピーコンストラクタを定義

明示的にデフォルト化された関数と暗黙的に宣言された関数は、まとめて デフォルト化された 関数と呼ばれます。それらの実際の定義は暗黙的に提供されます。詳細については、それぞれの対応するページを参照してください。

削除された関数

関数定義が構文 ( 4 ) または ( 5 ) (C++26以降) の場合、その関数は 明示的に削除された と定義されます。

削除された関数のあらゆる使用は不適格です(プログラムはコンパイルされません)。これには、明示的な呼び出し(関数呼び出し演算子を使用)と暗黙的な呼び出し(削除されたオーバーロード演算子、特殊メンバ関数、アロケーション関数などの呼び出し)、削除された関数へのポインタまたはメンバへのポインタの構築、および potentially-evaluated ではない式における削除された関数の使用も含まれます。

非純粋仮想メンバー関数は、暗黙的に odr-used されている場合でも、deletedとして定義することができます。deleted関数はdeleted関数によってのみオーバーライドでき、非deleted関数は非deleted関数によってのみオーバーライドできます。

string-literal が存在する場合、実装は削除の根拠を示す、または代替案を提案する結果の診断メッセージの一部としてそのテキストを含めることが推奨される。

(since C++26)

関数がオーバーロードされている場合、 オーバーロード解決 が最初に行われ、削除された関数が選択された場合にのみプログラムは不適格となります:

struct T
{
    void* operator new(std::size_t) = delete;
    void* operator new[](std::size_t) = delete("new[] is deleted"); // C++26以降
};
T* p = new T;    // エラー:削除されたT::operator newの呼び出しを試みる
T* p = new T[5]; // エラー:削除されたT::operator new[]の呼び出しを試みる
                 //        診断メッセージ「new[] is deleted」を出力

関数の削除定義は、翻訳単位内で最初の宣言でなければなりません:以前に宣言された関数を削除済みとして再宣言することはできません:

struct T { T(); };
T::T() = delete; // エラー: 最初の宣言で削除されなければならない

ユーザー提供関数

関数は、それがユーザー宣言されており、かつ最初の宣言で明示的にデフォルト化または削除されていない場合、 user-provided となります。ユーザー提供の明示的デフォルト化関数(すなわち、最初の宣言後に明示的にデフォルト化されたもの)は、明示的にデフォルト化された時点で定義されます。このような関数が暗黙的に削除済みとして定義される場合、プログラムは不適格となります。最初の宣言後に関数をデフォルト化として宣言することは、効率的な実行と簡潔な定義を提供するとともに、進化するコードベースへの安定したバイナリインターフェースを可能にします。

// 「trivial」のすべての特殊メンバ関数は
// それぞれ最初の宣言でデフォルト化されており、
// ユーザー提供ではない
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(); // 最初の宣言
};
// 最初の宣言でデフォルト化されておらず、
// ユーザー提供であり、ここで定義されている
nontrivial::nontrivial() = default;

曖昧性解決

関数本体と 初期化子 の間の曖昧性が生じる場合で、かつ { で始まる または = (C++26以降) 場合、この曖昧性は 宣言子識別子 の型をチェックすることで解決されます noptr-declarator :

  • 型が関数型の場合、曖昧なトークンシーケンスは関数本体として扱われます。
  • それ以外の場合、曖昧なトークンシーケンスは初期化子として扱われます。
using T = void(); // 関数型
using U = int;    // 非関数型
T a{}; // 何もしない関数を定義
U b{}; // intオブジェクトを値初期化
T c = delete("hello"); // 関数をdeleteとして定義
U d = delete("hello"); // intオブジェクトをdelete式の結果で
                       // コピー初期化(不適格)

__func__

関数本体内部では、関数ローカルで事前定義された変数 __func__ は、以下のように定義されているかのように扱われます

static const char __func__[] = "function-name";

この変数はブロックスコープと静的記憶域期間を持ちます:

struct S
{
    S(): s(__func__) {} // OK: 初期化リストは関数本体の一部
    const char* s;
};
void f(const char* s = __func__); // エラー: パラメータリストは宣言子の一部
#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;
}

出力例:

Foo Bar Pub ~Bar
(C++11以降)

関数契約指定子

関数宣言と lambda expressions は一連の function contract specifiers  を含むことができ、各指定子は以下の構文を持ちます:

pre attr  (オプション) ( predicate ) (1)
post attr  (オプション) ( predicate ) (2)
post attr  (オプション) ( identifier result-attr  (オプション) : predicate ) (3)
1) 事前条件アサーションを導入します。
2,3) ポストコンディション表明を導入する。
2) アサーションは結果にバインドされません。
3) アサーションは結果にバインドされます。
attr - 導入される契約表明に付属する属性のリスト
predicate - 任意の式(括弧で囲まれていない カンマ式 を除く)
identifier - 結果を参照する識別子
result-attr - 結果のバインディングに付属する属性のリスト


事前条件表明と事後条件表明は総称して 関数契約表明  と呼ばれます。

関数契約表明は、関数に関連付けられた 契約表明 です。関数契約表明の述語は、 述語 文脈的に変換 され bool 型となったものです。

以下の関数は関数契約指定子と共に宣言することはできません:

事前条件アサーション

事前条件アサーションは関数のエントリに関連付けられています:

int divide(int dividend, int divisor) pre(divisor != 0)
{
    return dividend / divisor;
}
double square_root(double num) pre(num >= 0)
{
    return std::sqrt(num);
}

事後条件アサーション

事後条件アサーションは、関数が正常に終了する際に関連付けられます。

事後条件アサーションに identifier が含まれている場合、関数契約指定子は identifier を関連する関数の 結果バインディング の名前として導入します。結果バインディングは、その関数の呼び出しによって返されるオブジェクトまたは参照を表します。結果バインディングの型は、関連する関数の戻り値の型です。

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))
        // 例外による終了は契約違反を引き起こさない
        throw std::invalid_argument("無効な引数");
    return std::sin(num);
}

事後条件表明が identifier を持つ場合、かつ関連する関数の戻り値の型が(possibly cv-qualified) void である場合、プログラムは不適格となります:

void f() post(r : r > 0); // エラー: 「r」に値をバインドできません

非テンプレート関数の宣言された戻り値の型に プレースホルダ型 が含まれる場合、 identifier を含む事後条件アサーションは関数定義内でのみ出現できます:

auto g(auto&) post(r : r >= 0); // OK、「g」はテンプレート
auto h() post(r : r >= 0);      // エラー: 戻り値に名前を付けることはできません
auto k() post(r : r >= 0)       // OK、「k」は定義
{
    return 0;
}

契約の一貫性

関数または関数テンプレート func 再宣言 D は、 contract-specs を持たないか、または D から到達可能な最初の宣言 F と同じ contract-specs を持たなければなりません。 D F が異なる翻訳単位にある場合、診断メッセージは D が名前付きモジュールに属している場合にのみ要求されます。

宣言 F1 が1つの翻訳単位における func の最初の宣言であり、宣言 F2 が別の翻訳単位における func の最初の宣言である場合、 F1 F2 は同じ contract-specs を指定しなければならない(診断は要求されない)。

2つの contract-specs は、同じ順序で同じ関数契約指定子で構成されている場合、同一と見なされます。

関数宣言 D1 上の関数契約指定子 C1 は、以下のすべての条件が満たされる場合、関数宣言 D2 上の関数契約指定子 C2 と同じです:

  • C1 C2 predicate は、宣言 D1 D2 の関数定義内に配置された場合( D1 D2 が異なる翻訳単位にある場合、各 predicate 内で定義された対応するエンティティは単一の定義を持つ単一のエンティティであるかのように振る舞う)、以下の名前変更を除いて one-definition rule を満たすものとする:
    • 宣言された関数のパラメータの名前変更。
    • 宣言された関数を囲むテンプレートのテンプレートパラメータの名前変更。
    • 結果バインディング(存在する場合)の名前変更。
  • C1 C2 の両方が identifier を持つか、どちらも持たない。

この条件が、単に述語内に含まれる2つのラムダ式の比較のみが原因で満たされない場合、 診断は要求されません。

bool b1, b2;
void f() pre (b1) pre([]{ return b2; }());
void f(); // OK、関数契約指定子が省略されています
void f() pre (b1) pre([]{ return b2; }()); // エラー:クロージャの型が異なります
void f() pre (b1); // エラー:関数契約指定子が異なります
int g() post(r : b1);
int g() post(b1); // エラー:結果のバインドがありません
namespace N
{
    void h() pre (b1);
    bool b1;
    void h() pre (b1); // エラー:関数契約指定子が
                       //        一定義規則に従って異なります
}
(C++26以降)

注記

直接初期化構文を使用した変数宣言と関数宣言の間で曖昧性がある場合、コンパイラは常に関数宣言を選択します。詳細は direct-initialization を参照してください。

機能テストマクロ 規格 機能
__cpp_decltype_auto 201304L (C++14) decltype(auto)
__cpp_return_type_deduction 201304L (C++14) 通常関数の戻り値型推論
__cpp_explicit_this_parameter 202110L (C++23) 明示的オブジェクトパラメータ ( this の推論 )
__cpp_deleted_function 202403L (C++26) 理由付き削除関数

キーワード

default , delete , pre , post

#include <iostream>
#include <string>
// デフォルト引数を持つシンプルな関数、戻り値なし
void f0(const std::string& arg = "world!")
{
    std::cout << "Hello, " << arg << '\n';
}
// 宣言は名前空間(ファイル)スコープ内
//(定義は後で提供)
int f1();
// f0へのポインタを返す関数、C++11以前のスタイル
void (*fp03())(const std::string&)
{
    return f0;
}
// f0へのポインタを返す関数、C++11の後置戻り値型
auto fp11() -> void(*)(const std::string&)
{
    return f0;
}
int main()
{
    f0();
    fp03()("test!");
    fp11()("again!");
    int f2(std::string) noexcept; // 関数スコープ内の宣言
    std::cout << "f2(\"bad\"): " << f2("bad") << '\n';
    std::cout << "f2(\"42\"): " << f2("42") << '\n';
}
// intを返すシンプルな非メンバ関数
int f1()
{
    return 007;
}
// 例外指定と関数tryブロックを持つ関数
int f2(std::string str) noexcept
try
{
    return std::stoi(str);
}
catch (const std::exception& e)
{
    std::cerr << "stoi() failed!\n";
    return 0;
}
// 削除された関数、呼び出しを試みるとコンパイルエラーになる
void bar() = delete
#   if __cpp_deleted_function
    ("reason")
#   endif
;

出力例:

stoi() failed!
Hello, world!
Hello, test!
Hello, again!
f2("bad"): 0
f2("42"): 42

不具合報告

以下の動作変更の欠陥報告書は、以前に公開されたC++規格に対して遡及的に適用されました。

DR 適用対象 公開時の動作 正しい動作
CWG 135 C++98 クラス内で定義されたメンバー関数は
自身のクラスをパラメータまたは戻り値の型として
持つことができなかった(不完全型のため)
許可
CWG 332 C++98 パラメータがCV修飾された void 型を持つことができた 禁止
CWG 393 C++98 未知のサイズの配列へのポインタ/参照を含む型は
パラメータとして使用できなかった
そのような型は許可
CWG 452 C++98 メンバー初期化子リストは関数本体の一部ではなかった 一部である
CWG 577 C++98 依存型 void を使用して
パラメータなしの関数を宣言できた
非依存
void のみ許可
CWG 1327 C++11 デフォルト化または削除された関数は
override または final で指定できなかった
許可
CWG 1355 C++11 特殊メンバー関数のみがユーザー提供可能だった すべての関数に拡張
CWG 1394 C++11 削除された関数は不完全型のパラメータを
持つことや不完全型を返すことができなかった
不完全型が許可
CWG 1824 C++98 関数定義のパラメータ型と戻り値型に対する完全性チェックが
関数定義のコンテキスト外で行われる可能性があった
関数定義の
コンテキスト内でのみ
チェック
CWG 1877 C++14 戻り値型推論が return ; return void ( ) ; として扱った この場合、単純に戻り値
型を void と推論
CWG 2015 C++11 削除された仮想関数の暗黙的なodr-useは不適格だった そのようなodr-useは
使用禁止から免除される
CWG 2044 C++14 void を返す関数に対する戻り値型推論は
宣言された戻り値型が decltype ( auto ) の場合に失敗した
このケースを処理するよう
推論規則を更新
CWG 2081 C++14 関数の再宣言は、最初の宣言で戻り値型推論を
使用していなくても使用できた
許可されない
CWG 2144 C++11 { } が同じ場所で関数本体または初期化子の両方になり得た 宣言子識別子の型によって
区別される
CWG 2145 C++98 関数定義内の 宣言子 を括弧で囲むことができなかった 許可
CWG 2259 C++11 括弧で囲まれた型名に関する曖昧性解決規則が
ラムダ式をカバーしていなかった
カバーされる
CWG 2430 C++98 クラス定義内のメンバー関数定義において、
そのクラスの型が戻り値型またはパラメータ型に
なれなかった( CWG issue 1824 の解決による)
関数本体内でのみ
チェック
CWG 2760 C++98 コンストラクタの関数本体は、コンストラクタの通常の
関数本体で指定されていない初期化を含まなかった
これらの初期化も
含む
CWG 2831 C++20 requires-clause を持つ関数定義が
非テンプレート関数を定義できた
禁止
CWG 2846 C++23 明示的オブジェクトメンバー関数はクラス外定義を持てなかった 許可
CWG 2915 C++23 無名の明示的オブジェクトパラメータが型 void を持つことができた 禁止

関連項目

C ドキュメント for 関数宣言