名前空間
変種
操作

関数宣言

提供: cppreference.com
< cpp‎ | language
 
 
C++言語
一般的なトピック
フロー制御
条件付き実行文
繰り返し文 (ループ)
ジャンプ文
関数
関数宣言
ラムダ関数宣言
inline 指定子
例外指定 (C++20未満)
noexcept 指定子 (C++11)
例外
名前空間
指定子
decltype (C++11)
auto (C++11)
alignas (C++11)
記憶域期間指定子
初期化
代替表現
リテラル
ブーリアン - 整数 - 浮動小数点
文字 - 文字列 - nullptr (C++11)
ユーザ定義 (C++11)
ユーティリティ
属性 (C++11)
typedef 宣言
型エイリアス宣言 (C++11)
キャスト
暗黙の変換 - 明示的な変換
static_cast - dynamic_cast
const_cast - reinterpret_cast
メモリ確保
クラス
クラス固有の関数特性
特別なメンバ関数
テンプレート
その他
 
 

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

目次

[編集] 関数宣言

関数宣言は任意のスコープに現れることができます。 クラススコープの関数宣言はクラスメンバ関数を導入します (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 の構文の他の形式については宣言を参照してください。)

1) 普通の関数宣言子の構文。
2) 後置戻り値型の宣言。 後置戻り値型は最も外側の関数宣言子に対してのみ使用できます。 この場合 decl-specifier-seq はキーワード 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 節を続けることができます。 これは関数に対して紐付ける制約を宣言します。 制約は関数がオーバーロード解決によって選択されるために満たされなければなりません (例えば void f1(int a) requires true;)。 紐付けられた制約は関数のシグネチャの一部ですが、関数の型の一部ではないことに注意してください。

(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 がキーワード 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 式の場合、推定される戻り値の型は decltype(auto) または auto (cv 修飾されていても構いません) のいずれかでなければなりません。 前者の場合、推定される戻り値の型は void です。 後者の場合、推定される戻り値の型は (同じに 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 の戻り値の型はすでに判明しています。
}

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)
... (5)
void (6)
1) 名前付きの (仮) 引数を宣言します。 decl-specifier-seq および declarator の意味については宣言を参照してください。
int f(int a, int *p, int (*(*x)(double))[3]);
2) 名前付きの (仮) 引数をデフォルト値付きで宣言します。
int f(int a = 7, int *p = nullptr, int (*(*x)(double))[3] = nullptr);
3) 名前なしの引数を宣言します。
int f(int, int *, int (*(*)(double))[3]);
4) 名前なしの引数をデフォルト値付きで宣言します。
int f(int = 7, int * = nullptr, int (*(*)(double))[3] = nullptr);
5) 可変長引数関数を宣言します。 引数リストの最後の引数としてのみ現れることができます。
int printf(const char* fmt, ...);
6) 関数が引数を取らないことを表します。 空の引数リストの正確な同義語です。 int f(void);int f(); は同じ関数を宣言します。 それ以外では void 型 (cv 修飾されている場合を含みます) は引数リストで使用できないことに注意してください。 int f(void, int);int f(const void); はエラーです (void* などの派生型は使用できます)。 テンプレートでは、非依存な void 型のみが使用できます (型 T の引数1個を取る関数は、 T = void で実体化された場合、引数なしの関数になりません)。 (C++11以上)

decl-specifier-seq は型指定子以外の指定子が存在し得ることを暗示しますが、他に指定可能な指定子は register auto (C++11未満) だけであり、効果はありません。

(C++17未満)

関数の引数のいずれかがプレースホルダ (auto またはコンセプト型のいずれか) を使用する場合、それは関数の宣言ではなく、省略形の関数テンプレートの宣言です。

void f1(auto);    // template<class T> void f(T) と同じです。
void f2(C1 auto); // template<C1 T> void f7(T) と同じです (C1 がコンセプトの場合)。
(C++20以上)

関数の宣言で宣言される引数の名前は、通常、自己文書化の目的のためだけです。 これらは関数の定義で使用されます (が、オプショナルなままです)。

引数リストのそれぞれの関数の引数の型は、以下のルールに従って決定されます。

1) まず、型を決定するために、宣言の場合と同様に、 decl-specifier-seq と宣言子が組み合わされます。
2) 型が「T の配列」または「境界が未知な T の配列」の場合、それは「T へのポインタ」型に置き換えられます。
3) 型が関数型 F の場合、それは「F へのポインタ」型に置き換えられます。
4) トップレベルの cv 修飾子が引数の型から取り除かれます (この調節は関数の型にのみ影響しますが、引数の性質は変更しません。 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......);

そのような宣言が使用される例は、 std::is_function の実装です。

[編集] 関数定義

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

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以上)
1) 普通の関数の本体。
2) 関数 try ブロック (try/catch ブロックでラップされた普通の関数の本体)。
3) 明示的に削除された関数定義。
4) 明示的にデフォルト化された関数定義。 特別なメンバ関数に対してのみ使用できます。
attr(C++11) - オプショナルな属性のリスト。 これらの属性は declarator 内の識別子の後の属性 (もしあれば) と組み合わされます (このページのトップを参照してください)。
decl-specifier-seq - 指定子付きの戻り値の型。 宣言の文法の場合と同様です。
declarator - 関数宣言子。 上記の関数宣言の文法の場合と同じです。 関数宣言の場合と同様に、後ろに requires-clause を続けることができます。 (C++20以上)
virt-specifier-seq(C++11) - overridefinal またはその順不同の組み合わせ (メンバ関数に対してのみ使用できます)。
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 です。
}

削除された関数

関数本体の代わりに特別な構文 = delete ; が使用された場合、関数は削除されたものとして定義されます。 削除された関数はいかなる使用も ill-formed です (プログラムはコンパイルできません)。 これは明示的な呼び出し (関数呼び出し演算子を用いた)、暗黙の呼び出し (オーバーロード演算子、特別なメンバ関数、確保関数などの呼び出し)、削除された関数へのポインタまたはメンバへのポインタの構築、および未評価式における削除された関数の使用さえも含みます。 しかし、期せずして削除された非純粋仮想メンバ関数の暗黙の ODR 使用は許されます。

関数がオーバーロードされている場合は、まずオーバーロード解決が行われ、そして削除された関数が選択された場合にのみ、プログラムは 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以上)

[編集] ノート

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

[編集]

#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