名前空間
変種
操作

関数宣言

From cppreference.com
< cpp‎ | language
 
 
C++言語
全般
フロー制御
条件実行文
if
繰り返し文 (ループ)
for
範囲for (C++11)
ジャンプ文
関数
関数宣言
ラムダ式
inline指定子
動的例外仕様 (C++17まで*)
noexcept指定子 (C++11)
例外
名前空間
基本型
列挙型
関数型
指定子
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
記憶域期間指定子
初期化
代替表現
リテラル
ブーリアン - 整数 - 浮動小数点数
文字 - 文字列 - nullptr (C++11)
ユーザー定義 (C++11)
ユーティリティ
属性 (C++11)
typedef宣言
型エイリアス宣言 (C++11)
キャスト
メモリ確保
クラス
クラス固有の関数プロパティ
explicit (C++11)
static

特殊メンバ関数
テンプレート
その他
 
 

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

目次

[編集] 関数宣言

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

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

(declarator 構文の他の形式については宣言を参照)

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

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

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

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

(C++20以降)

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

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

[[noreturn]] void f [[noreturn]] (); // OK: both attributes apply to the function f

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

void f() [[noreturn]]; // Error: this attribute has no effect on the function itself
(C++11以降)

戻り値の型推論

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

int x = 1;
auto f() { return x; }        // return type is int
const auto& f() { return x; } // return type is const int&

戻り値の型がdecltype(auto)の場合、戻り値の型は、return文で使用される式がdecltypeでラップされた場合に得られるものと同じになります。

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))

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

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

auto f(bool val)
{
    if (val) return 123; // deduces return type int
    else return 3.14f;   // Error: deduces return type float
}

return文がない場合、またはreturn文の引数がvoid式である場合、宣言された戻り値の型はdecltype(auto)である必要があります。この場合、推論された戻り値の型はvoidになります。あるいは(cv修飾されていてもよい)autoである必要があります。この場合、推論された戻り値の型は(同様にcv修飾された)voidになります。

auto f() {}              // returns void
auto g() { return f(); } // returns void
auto* x() {}             // Error: cannot deduce auto* from void

関数内で一度return文が確認されると、その文から推論された戻り値の型は、他のreturn文を含む関数の残りの部分で使用できます。

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
}

return文がブレースで囲まれた初期化子リストを使用する場合、推論は許可されません。

auto func() { return {1, 2, 3}; } // Error

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

struct F
{
    virtual auto f() { return 2; } // Error
};

ユーザー定義変換関数以外の関数テンプレートは戻り値の型推論を使用できます。return文の式が依存しなくても、推論はインスタンス化時に行われます。このインスタンス化は、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

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

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

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

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

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

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

[編集] パラメータリスト

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

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-seqdeclaratorの意味については、宣言を参照してください。
int f(int a, int* p, int (*(*x)(double))[3]);
2) 名前付きの明示的オブジェクトパラメータを宣言します。
3) デフォルト値を持つ名前付き(仮)パラメータを宣言します。
int f(int a = 7, int* p = nullptr, int (*(*x)(double))[3] = nullptr);
4) 無名パラメータを宣言します。
int f(int, int*, int (*(*)(double))[3]);
5) 無名の明示的オブジェクトパラメータを宣言します。
6) デフォルト値を持つ無名パラメータを宣言します。
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); (ここで Tvoid です)
void パラメータは明示的オブジェクトパラメータです (C++23以降) int f5(this void);

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

(C++17まで)

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

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))を持つパラメータ宣言は、明示的オブジェクトパラメータを宣言します。

明示的オブジェクトパラメータは関数パラメータパックにすることはできず、以下の宣言のパラメータリストの最初のパラメータとしてのみ現れることができます。

明示的なオブジェクトパラメータを持つメンバー関数には、以下の制限があります。

  • その関数は静的ではありません。
  • その関数は仮想ではありません。
  • 関数の宣言子にはcvrefが含まれません。
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から)

関数宣言で宣言されたパラメータ名は、通常、自己文書化の目的でしかありません。それらは関数定義で使用されます(ただし、オプションのままです)。

パラメータリスト内で型名が括弧で囲まれている場合(ラムダ式を含む(C++11以降))に曖昧さが生じます。この場合、関数へのポインタ型のパラメータの宣言と、declaratorの識別子を余分な括弧で囲んだパラメータの宣言との間で選択が行われます。解決策は、型名を単純型指定子(関数ポインタ型)とみなすことです。

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]);

パラメータ型は、参照や不明な境界の配列へのポインタ、そのような型の多重ポインタ/配列、またはパラメータがそのような型である関数へのポインタを含む型にすることはできません。

[編集] 省略記号の使用

パラメータリストの最後のパラメータは省略記号(...)にすることができます。これは可変引数関数を宣言します。省略記号の前のコンマは省略できます(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

[編集] 関数型

[編集] パラメータ型リスト

関数のパラメータ型リストは次のように決定されます。

  1. 各パラメータの型(関数パラメータパックを含む)(C++11以降)は、それぞれのパラメータ宣言から決定されます。
  2. 各パラメータの型を決定した後、「Tの配列」型または関数型Tのパラメータは、「Tへのポインタ」に調整されます。
  3. パラメータ型リストを作成した後、関数型を形成する際に、パラメータ型を変更するトップレベルのcv修飾子は削除されます。
  4. 結果として得られる変換されたパラメータ型のリストと、省略記号または関数パラメータパック(C++11以降)の有無が、関数のパラメータ型リストとなります。
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

[編集] 関数型の決定

構文(1)では、noptr-declaratorを独立した宣言と仮定し、noptr-declarator内のqualified-idまたはunqualified-idの型を「derived-declarator-type-list T」とすると、

  • 例外指定が非スローである場合、宣言された関数の型は次のようになります。
    「derived-declarator-type-list noexcept function of
    parameter-type-list cv (オプション) ref  (オプション) returning 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」とすると(この場合Tautoでなければなりません)、

(C++11以降)
  • 例外指定が非スローである場合、宣言された関数の型は次のようになります。
    「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以降)
// 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;
};

[編集] 後続の修飾子

cv またはref  (C++11以降)を持つ関数型(typedef名で命名された型を含む)は、次の場合にのみ現れます。

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

[編集] 関数シグネチャ

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

関数のシグネチャは、その名前とパラメータ型リストで構成されます。そのシグネチャには、囲んでいる名前空間も含まれますが、以下の例外があります。

  • 関数がメンバー関数である場合、そのシグネチャには、囲む名前空間の代わりに、関数がメンバーであるクラスが含まれます。そのシグネチャには、存在する場合、以下のコンポーネントも含まれます。
  • cv
  • ref
(C++11以降)
  • 後続のrequires
  • 関数が、後続のrequires句を持つ非テンプレートフレンド関数である場合、そのシグネチャには、囲む名前空間の代わりに、囲むクラスが含まれます。シグネチャには、後続のrequires句も含まれます。
(C++20以降)

exceptattr(C++11以降)は関数シグネチャには関与しませんが、noexcept指定は関数型に影響を与えます(C++17以降)

[編集] 関数定義

非メンバー関数定義は名前空間スコープでのみ現れます(ネストされた関数はありません)。メンバー関数定義は、クラス定義の本体内にも現れることがあります。これらは以下の構文を持ちます。

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以降) overridefinal、またはそれらの任意の順序での組み合わせ
requires-clause - requires
contract-specs - (C++26以降) 関数契約指定子のリスト
関数本体 - 関数本体 (以下参照)


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

ctor-initializer (オプション) compound-statement (1)
function-try-block (2)
= default ; (3) (C++11以降)
= delete ; (4) (C++11以降)
= delete ( string-literal ); (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 is “int”
// declarator is “max(int a, int b, int c)”
// body is { ... }

関数本体は複合文(一対の波括弧で囲まれた0個以上の文のシーケンス)であり、関数が呼び出されたときに実行されます。さらに、コンストラクタの関数本体には以下のものも含まれます。

関数定義にvirt-specsが含まれる場合、それはメンバー関数を定義しなければなりません。

(C++11以降)

関数定義にrequires-clauseが含まれる場合、それはテンプレート関数を定義しなければなりません。

(C++20以降)
void f() override {} // Error: not a member function
 
void g() requires (sizeof(int) == 4) {} // Error: not a templated function

関数定義のパラメータ型、および戻り値の型は、(cv修飾されていてもよい)不完全なクラス型にすることはできません(関数が削除として定義されている場合を除く)(C++11以降)。完全性のチェックは関数本体内でのみ行われるため、メンバー関数は、定義時点では不完全であっても(関数本体内では完全です)、定義されているクラス(またはその囲むクラス)を返すことができます。

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

void print(int a, int) // second parameter is not used
{
    std::printf("a = %d\n", a);
}

パラメータのトップレベルのcv修飾子は関数宣言では破棄されますが、関数本体で可視となるパラメータの型を変更します。

void f(const int n) // declares function of type void(int)
{
    // but in the body, the type of “n” is const int
}

デフォルト化された関数

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

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

明示的にデフォルト化された特殊メンバー関数 F1 は、対応する暗黙的に宣言されるはずだった特殊メンバー関数 F2 と、以下の点で異なることがあります。

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

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

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

最初の宣言で明示的にデフォルト化された関数は暗黙的にインラインであり、constexpr関数になり得る場合は暗黙的にconstexprです。

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

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

削除された関数

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

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

純粋仮想ではないメンバー関数は、暗黙的にODR-usedされるにもかかわらず、削除済みとして定義できます。削除済み関数は削除済み関数によってのみオーバーライドでき、非削除済み関数は非削除済み関数によってのみオーバーライドできます。

string-literalが存在する場合、実装は、削除の理由を説明したり代替案を提案したりする診断メッセージの一部としてそのテキストを含めることを推奨します。

(C++26以降)

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

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”

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

struct T { T(); };
T::T() = delete; // Error: must be deleted on the first declaration

ユーザー定義関数

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

// 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;

曖昧性解決

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

  • 型が関数型である場合、曖昧なトークンシーケンスは関数本体として扱われます。
  • それ以外の場合、曖昧なトークンシーケンスは初期化子として扱われます。
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__

関数本体内では、関数ローカルの事前定義変数__func__は、次のように定義されます。

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

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

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

実行結果の例

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

関数契約指定子

関数宣言とラムダ式は、関数契約指定子のシーケンスを含むことができます。各指定子は以下の構文を持ちます。

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 - 結果バインディングに適用される属性のリスト


事前条件アサーションと事後条件アサーションは、総称して関数契約アサーションと呼ばれます。

関数契約アサーションは、関数に関連付けられた契約アサーションです。関数契約アサーションの述語は、そのpredicateboolコンテキスト的に変換したものです。

以下の関数は、関数契約指定子を使って宣言することはできません。

事前条件アサーション

事前条件アサーションは、関数に入るときに関連付けられます。

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))
        // exiting via an exception never causes contract violation
        throw std::invalid_argument("Invalid argument");
    return std::sin(num);
}

事後条件アサーションにidentifier があり、関連する関数の戻り値の型が(cv修飾されている可能性のある)voidである場合、プログラムは不正となります。

void f() post(r : r > 0); // Error: no value can be bound to “r”

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

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

契約の一貫性

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

ある翻訳単位における関数funcの最初の宣言がF1であり、別の翻訳単位における関数funcの最初の宣言がF2である場合、F1F2は同じcontract-specs を指定しなければなりません。診断は必要ありません。

2つのcontract-specs は、同じ関数契約指定子を同じ順序で含む場合、同じであるとみなされます。

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

  • C1C2predicate は、それぞれ宣言D1D2の関数定義に置かれた場合(D1D2が異なる翻訳単位にある場合、各predicate内で定義された対応するエンティティは、単一の定義を持つ単一のエンティティであるかのように振る舞います)、以下の名前変更を除いて単一定義規則を満たします。
    • 宣言された関数のパラメータの名前変更。
    • 宣言された関数を囲むテンプレートのテンプレートパラメータの名前変更。
    • 結果バインディング(もしあれば)の名前変更。
  • C1C2の両方にidentifierがあるか、どちらにもないかのいずれかであること。

この条件がpredicate 内に含まれる2つのラムダ式の比較のみによって満たされない場合、診断は必要ありません。

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

[編集] 備考

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

機能テストマクロ 規格 機能
__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>
 
// 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
;

実行結果の例

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 関数定義のdeclaratorは括弧で囲むことができませんでした。 許可
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型を持つことができました。 禁止された

[編集] 関連項目

English 日本語 中文(简体) 中文(繁體)