関数宣言
関数宣言は関数の名前とその型を導入します。 関数定義は関数の名前/型を関数の本体と紐付けます。
関数宣言
関数宣言は任意のスコープに現れることができます。 クラススコープの関数宣言はクラスメンバ関数を導入します (friend 指定子が使用された場合を除きます) (詳細はメンバ関数およびフレンド関数を参照してください)。
宣言されている関数の型は戻り値の型 (宣言の構文の decl-specifier-seq によって提供されます) および関数の declarator から構成されます。
noptr-declarator ( parameter-list ) cv(オプション) ref(オプション) except(オプション) attr(オプション)
|
(1) | ||||||||
noptr-declarator ( parameter-list ) cv(オプション) ref(オプション) except(オプション) attr(オプション) -> trailing
|
(2) | (C++11以上) | |||||||
(declarator の構文の他の形式については宣言を参照してください。)
auto を含まなければなりません。| noptr-declarator | - | 任意の有効な declarator。 ただし *、 & または && で始まる場合は括弧で囲む必要があります。 |
| parameter-list | - | 関数の引数のコンマ区切りのリスト (空でも構いません) (詳細は後述)。 |
| attr(C++11) | - | オプショナルな属性のリスト。 これらの属性は関数自身ではなく関数の型に適用されます。 関数に対する属性は宣言子内の識別子の後に現れ、宣言の先頭に現れる属性 (もしあれば) と組み合わされます。 |
| cv | - | const/volatile 修飾。 非静的メンバ関数の宣言でのみ使用できます。 |
| ref(C++11) | - | 参照修飾。 非静的メンバ関数の宣言でのみ使用できます。 |
| except | - | 動的例外指定(C++17未満)または noexcept 指定子(C++11)のいずれか。 例外指定は関数の型の一部ではないことに注意してください。 (C++17未満) |
| trailing(C++11) | - | 後置戻り値型。 template <class T, class U> auto add(T t, U u) -> decltype(t + u); のように戻り値型が引数名に依存する場合や、 auto fpif(int)->int(*)(int) のように複雑な場合に便利です。
|
|
宣言で述べられているように、宣言子は後ろに requires 節を続けることができます。 これは関数に対して紐付ける制約を宣言します。 制約は関数がオーバーロード解決によって選択されるために満たされなければなりません (例えば |
(C++20以上) |
関数の宣言子は、 decl-specifier-seq が許す場合は、他の宣言子と混ぜることができます。
// 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; // コンパイル時エラー、 (decl-specifier-seq 内の) virtual は
// 非静的メンバ関数の宣言でのみ使用できます。
};
関数の戻り値の型を関数型または配列型にすることはできません (が、それらへのポインタまたは参照にすることはできます)。
|
あらゆる宣言と同様に、その宣言の前に現れる属性および宣言子内の識別子の直後に現れる属性はどちらも宣言または定義されているエンティティ (この場合は関数) に適用されます。 [[noreturn]] void f [[noreturn]] (); // OK、両方の属性が関数 f に適用されます。
しかし、 (上の構文の) 宣言子の後に現れる属性は、関数自身ではなく関数の型に適用されます。 void f() [[noreturn]]; // エラー、この属性は型に対しては効果がありません。
|
(C++11以上) |
あらゆる宣言と同様に、 ret func(params) として宣言された関数 func の型は ret(params) です (上で説明されている引数の型の書き換えは除きます) (型の名前付けを参照してください)。
戻り値の型の推定関数宣言の decl-specifier-seq がキーワード int x = 1;
auto f() { return x; } // 戻り値の型は int です。
const auto& f() { return x; } // 戻り値の型は const int& です。
戻り値の型が int x = 1;
decltype(auto) f() { return x; } // 戻り値の型は int (decltype(x) と同じ) です。
decltype(auto) f() { return(x); } // 戻り値の型は int& (decltype((x)) と同じ) です。
(ノート: 「 複数の return 文がある場合は、すべて同じ型に推定されなければなりません。 auto f(bool val)
{
if (val) return 123; // 戻り値の型は int と推定されます。
else return 3.14f; // エラー、戻り値の型は float と推定されます。
}
return 文がない場合または return 文の引数が 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 の戻り値の型はすでに判明しています。
}
return 文が波括弧初期化子リストを使用する場合、推定はできません。 auto func () { return {1, 2, 3}; } // エラー。
仮想関数は戻り値の型の推定を使用できません。 struct F
{
virtual auto f() { return 2; } // エラー。
};
関数が戻り値の型の推定を使用する場合は、たとえ同じ型に推定される場合でも、推定後の型や別の種類の戻り値の型の推定を用いて再宣言することはできません。 auto f(); // 宣言されたけれどもまだ定義されていません。
auto f() { return 42; } // 定義。 戻り値の型は int です。
int f(); // エラー、推定後の型は使用できません。
decltype(auto) f(); // エラー、異なる種類の推定。
auto f(); // OK、再宣言。
template<typename T>
struct A { friend T frf(T); };
auto frf(int i) { return i; } // A<int> のフレンドではありません。
ユーザ定義変換関数以外の関数テンプレートは戻り値の型の推定を使用できます。 推定は、たとえ 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つめのテンプレートオーバーロードを選択します。
戻り値の型の推定を用いる関数テンプレートの特殊化は、同じ戻り値の型のプレースホルダを使用しなければなりません。 template<typename T> auto g(T t) { return t; } // #1
template auto g(int); // OK、戻り値の型は int です。
//template char g(char); // エラー、マッチするテンプレートがありません。
template<> auto g(double); // OK、戻り値の型が不明な前方宣言。
template<typename T> T g(T t) { return t; } // OK、 #1 と同等ではありません。
template char g(char); // OK、今回はマッチするテンプレートがあります。
template auto g(float); // まだ #1 にマッチします。
//void h() { return g(42); } // エラー、曖昧です。
明示的実体化の宣言それ自身は戻り値の型の推定を使用する関数テンプレートを実体化しません。 template<typename T> auto f(T t) { return t; }
extern template auto f(int); // f<int> を実体化しません。
int (*p)(int) = f; // 戻り値の型を決定するために f<int> を実体化しますが、
// プログラム内のどこかに明示的実体化の定義が
// 必要なことは変わりません。
|
(C++14以上) |
引数リスト
引数リストはその関数が呼ばれるときに指定できる引数を決定します。 これは引数宣言のコンマ区切りのリストであり、そのそれぞれは以下の構文を持ちます。
| attr(オプション) decl-specifier-seq declarator | (1) | ||||||||
attr(オプション) decl-specifier-seq declarator = initializer
|
(2) | ||||||||
| attr(オプション) decl-specifier-seq abstract-declarator(オプション) | (3) | ||||||||
attr(オプション) decl-specifier-seq abstract-declarator(オプション) = initializer
|
(4) | ||||||||
void
|
(5) | ||||||||
int f(int a, int *p, int (*(*x)(double))[3]);
int f(int a = 7, int *p = nullptr, int (*(*x)(double))[3] = nullptr);
int f(int, int *, int (*(*)(double))[3]);
int f(void); と int f(); は同じ関数を宣言します。 それ以外では void 型 (cv 修飾されている場合を含みます) は引数リストで使用できないことに注意してください。 int f(void, int); や int f(const void); はエラーです (void* などの派生型は使用できます)。 テンプレートでは、非依存な void 型のみが使用できます (型 T の引数1個を取る関数は、 T = void で実体化された場合、引数なしの関数になりません)。 (C++11以上)引数リストの最後に省略記号 ... を付けても構いません。 これは可変長引数関数を宣言します。
int printf(const char* fmt ...);
C89 との互換性のため、引数リストに少なくとも1個の引数がある場合は、省略記号の前にオプショナルなコンマを付けても構いません。
int printf(const char* fmt, ...); // OK、上と同じです。
|
decl-specifier-seq は型指定子以外の指定子が存在し得ることを暗示しますが、他に指定可能な指定子は |
(C++17未満) |
|
関数の引数のいずれかがプレースホルダ ( void f1(auto); // template<class T> void f(T) と同じです。
void f2(C1 auto); // template<C1 T> void f7(T) と同じです (C1 がコンセプトの場合)。
|
(C++20以上) |
関数の宣言で宣言される引数の名前は、通常、自己文書化の目的のためだけです。 これらは関数の定義で使用されます (が、オプショナルなままです)。
引数リストのそれぞれの関数の引数の型は、以下のルールに従って決定されます。
int f(const int p, decltype(p)*); と int f(int, const int*); は同じ関数を宣言します) 。これらのルールにより、以下の関数宣言は正確に同じ関数を宣言しています。
int f(char s[3]);
int f(char[]);
int f(char* s);
int f(char* const);
int f(char* volatile s);
以下の宣言も正確に同じ関数を宣言しています。
int f(int());
int f(int (*g)());
|
境界が不明な配列への参照またはポインタ (そのような型の多段ポインタ/配列、または引数がそのような型の関数へのポインタを含みます) を含む型を引数の型とすることはできません。 |
(C++14未満) |
|
可変長引数を表す省略記号は、たとえパラメータパックの展開を表す省略記号の後であっても、コンマを前につける必要はありません。 そのため、以下の関数テンプレートは正確に同じです。 template<typename ...Args> void f(Args..., ...);
template<typename ...Args> void f(Args... ...);
template<typename ...Args> void f(Args......);
そのような宣言が使用される例は、 |
(C++11以上) |
関数定義
非メンバ関数の定義は名前空間スコープにのみ現れることができます (ネストした関数は存在しません)。 メンバ関数の定義はクラス定義の本体内にも現れることができます。 これらは以下の構文を持ちます。
| attr(オプション) decl-specifier-seq(オプション) declarator virt-specifier-seq(オプション) function-body | |||||||||
ただし function-body は以下のいずれかです。
| ctor-initializer(オプション) compound-statement | (1) | ||||||||
| function-try-block | (2) | ||||||||
= delete ;
|
(3) | (C++11以上) | |||||||
= default ;
|
(4) | (C++11以上) | |||||||
| attr(C++11) | - | オプショナルな属性のリスト。 これらの属性は declarator 内の識別子の後の属性 (もしあれば) と組み合わされます (このページのトップを参照してください)。 |
| decl-specifier-seq | - | 指定子付きの戻り値の型。 宣言の文法の場合と同様です。 |
| declarator | - | 関数宣言子。 上記の関数宣言の文法の場合と同じです。 関数宣言の場合と同様に、後ろに requires-clause を続けることができます。 (C++20以上) |
| virt-specifier-seq(C++11) | - | override、 final またはその順不同の組み合わせ (メンバ関数に対してのみ使用できます)。 |
| ctor-initializer | - | メンバ初期化子リスト。 コンストラクタでのみ使用できます。 |
| compound-statement | - | 関数の本体を構成する、波括弧で囲まれた文の並び。 |
int max(int a, int b, int c)
{
int m = (a > b)? a : b;
return (m > c)? m : c;
}
// decl-specifier-seq は「int」です。
// 宣言子は「max(int a, int b, int c)」です。
// 本体は { ... } です。
関数の本体は複文 (波括弧の組で囲まれた0個以上の文の並び) であり、関数呼び出しが行われたときに実行されます。
関数の引数の型および戻り値の型は、削除された関数を除き (C++11以上)不完全クラス型にできません。 完全性のチェックは関数の本体の文脈で行われます。 これは、たとえ定義の時点で不完全であっても (関数の本体内では完全です)、メンバ関数がそれらの定義されているクラス (または囲っているクラス) を返すことを許容します。
関数定義の declarator 内で宣言された引数は本体のスコープで定義されます。 引数が関数の本体で使用されない場合は、名前を付ける必要はありません (抽象宣言子を使用するにはそれで十分です)。
void print(int a, int) // 第2引数は使用されません。
{
std::printf("a = %d\n",a);
}
たとえ引数に対するトップレベルの cv 修飾子が関数宣言では破棄されるとしても、それらは関数の本体では引数の型を可視なものとして変更します。
void f(const int n) // void(int) 型の関数を宣言します。
{
// しかし本体では、 n の型は const int です。
}
削除された関数関数本体の代わりに特別な構文 関数がオーバーロードされている場合は、まずオーバーロード解決が行われ、そして削除された関数が選択された場合にのみ、プログラムは ill-formed です。 struct sometype
{
void* operator new(std::size_t) = delete;
void* operator new[](std::size_t) = delete;
};
sometype* p = new sometype; // エラー、削除された sometype::operator new を
// 呼び出そうとしています。
関数の削除された定義は翻訳単位内の最初の宣言でなければなりません。 以前に宣言された関数を削除されたものとして再定義することはできません。 struct sometype { sometype(); };
sometype::sometype() = delete; // エラー、最初の宣言で削除されなければなりません。
__func__関数の本体内で、関数ローカルな定義済みの変数 __func__ が、以下のように定義されているかのように定義されます。 static const char __func__[] = "function-name";
この変数はブロックスコープと静的記憶域期間を持ちます。 struct S
{
S(): s(__func__) {} // OK、初期化子リストは関数本体の一部です。
const char* s;
};
void f(const char* s = __func__); // エラー、引数リストは宣言子の一部です。
|
(C++11以上) |
ノート
直接初期化の構文を用いた変数の宣言と関数の宣言の間で曖昧な場合、コンパイラは常に関数の宣言を選択します。 直接初期化を参照してください。
例
| This section is incomplete Reason: move/cleanup |
#include <iostream>
#include <string>
// 名前空間 (ファイル) スコープでの宣言
// (定義は後で提供されます)。
int f1();
// デフォルト引数を持ち何も返さない単純な関数。
void f0(const std::string& arg = "world")
{
std::cout << "Hello, " << arg << '\n';
}
// f0 へのポインタを返す関数 (C++11 スタイル)。
auto fp11() -> void(*)(const std::string&)
{
return f0;
}
// f0 へのポインタを返す関数 (C++03 スタイル)。
void (*fp03())(const std::string&)
{
return f0;
}
int main()
{
f0();
fp11()("test");
fp03()("again");
int f2(std::string); // 関数スコープでの宣言。
std::cout << f2("bad12") << '\n';
}
// int を返す単純な非メンバ関数。
int f1()
{
return 42;
}
// 例外指定と関数 try ブロックを持つ関数。
int f2(std::string str) noexcept try
{
return std::stoi(str);
}
catch(const std::exception& e)
{
std::cerr << "stoi() failed!\n";
return 0;
}
出力:
Hello, world
Hello, test
Hello, again
stoi() failed!
0
欠陥報告
以下の動作変更欠陥報告は以前に発行された C++ 標準に遡って適用されました。
| DR | 適用先 | 発行時の動作 | 正しい動作 |
|---|---|---|---|
| CWG 1394 | C++11 | deleted function could not return an incomplete type | incomplete return type allowed |
| CWG 577 | C++11 | dependent type void could be used to declare a no-parameter function
|
only non-dependent void is allowed |
| CWG 393 | C++14 | types that include pointers/references to array of unknown bound can't be parameters | such types are allowed |
関連項目
関数宣言 の C言語リファレンス
|