web-dev-qa-db-ja.com

関数を返すC ++関数

標準では、許可されていない関数を返す関数はどこにありますか?それらは概念的にばかげていると理解していますが、文法がそれらを許可するように思えます。このWebページによると、「 noptr-declarator [is] any valid declarator 」には、関数の宣言子が含まれます。

_int f()();
_

構文について

[dcl.decl]に記述されている構文は、

_int f(char)(double)
_

関数fそれはacharとして解釈され、同じ署名を持つ関数を返します = int g(double)

_1    declarator:
2       ptr-declarator
3       noptr-declarator parameters-and-qualifiers trailing-return-type
4    ptr-declarator:
5        noptr-declarator
6        ptr-operator ptr-declarator
7    noptr-declarator:
8        declarator-id attribute-specifier-seq opt
9        noptr-declarator parameters-and-qualifiers
10       noptr-declarator [ constant-expression opt ] attribute-specifier-seq opt
11       ( ptr-declarator )
12    parameters-and-qualifiers:
13       ( parameter-declaration-clause ) cv-qualifier-seqAfter
_

大まかに言えば、1-> 2、2 = 4、4-> 6、4-> 6の後に、ptr-operator ptr-operator ptr-operatorが必要です。次に、4-> 5、5 = 7、7-> 8を使用します。最初の宣言子; 2番目と3番目の宣言子には4-> 5、5 = 7、7-> 9を使用します。

28
Hector

[dcl.fct]から、かなり明確に:

関数は、配列型または関数型の戻り値の型を持たない。ただし、型ポインタの戻り値型またはそのようなものへの参照を持つ場合がある。関数へのポインタの配列は存在できますが、関数の配列はありません。

C++ 11では、おそらく次のことが必要です。

_std::function<int()> f();
std::function<int(double)> f(char);
_

C++の文法に関して混乱があります。ステートメントint f(char)(double);は、文法に従って解析できます。解析ツリーは次のとおりです。

grammar

さらに、このような解析は、[dcl.fct]/1に基づいて意味があります。

宣言_T D_で、Dの形式は
_D1_(parameter-declaration-clausecv-qualifier-seqopt
ref-qualifieropt 例外仕様opt attribute-specifier-seqopt
および宣言_T D1_に含まれるdeclarator-idのタイプは、「derived-declarator-type-list T」、Ddeclarator-idのタイプは「derived- declarator-type-listfunction of(parameter-declaration-clausecv-qualifier-seqopt ref-qualifieropt T」を返します。

この例では、_T == int_、D == f(char)(double)D1 == f(char)です。 _T D1_(int f(char))のdeclarator-idのタイプは、「intを返す(char)の関数」です。 derived-declarator-type-listは「(char)が返す関数」です。したがって、fの型は、「(char)の関数が(double)のintを返す関数」として読み取られます。

これは明示的に禁止されている宣言子形式であるため、最終的には何も問題になりません。しかし、文法ではありません。

54
Barry

C++ 11 (ただし、以前のバージョンのC++ではない)を使用すると、Cのような関数ポインターだけでなく、C++ closures 、特に 無名関数 を使用します。 std :: function も参照してください

標準では、(semantically、構文的にではなく-(notgrammarの質問;引用については Barry's answer を参照)関数(および関数のsizeofも禁止!)が、関数ポインタを返すことを許可します。

ところで、私はあなたが関数全体を返すことができるとは思わない。それはどういう意味ですか?どのように実装しますか?実際には、関数はコードブロックであり、その名前は(配列のように)関数のマシンコードの開始点へのポインターです。

ナイストリックは、(C++標準のメカニズムoutsideを使用して)実行時に関数を作成することです(そして、その関数ポインターを処理します)。一部の外部ライブラリはそれを許可する場合があります:JITライブラリを使用できます(例: asmjitgccjit[〜#〜] llvm [〜#〜] ...)または単にC++コードを生成し、コンパイルして dlopendlsym POSIXシステムなどで.

PS。 C++ 11 grammar(標準のEBNFルール)が関数を返すことを許可しないことを理解することはおそらく正しいでしょう。それはセマンティックルールであり、それを許可しない平易な英語で記述されています(not文法ルールです)。つまり、EBNFだけで以下が可能になるということです。

 // semantically wrong... but perhaps not syntactically
 typedef int sigfun_T(std::string);
 sigfun_T foobar(int);

コンパイラが上記のコードを正しく拒否しているのは、 semantics 理由(EBNFルールではなく)のためです。実際には、 symbol table はC++コンパイラにとって非常に重要です(そして、それはnot構文または文脈自由文法です)。

C++の悲しい事実は、(レガシーの理由で)その文法(単独)が非常にあいまいであることです。したがって、C++ 11は、読みにくい(人間の場合)、書くのが難しい(開発者の場合)、解析するのが難しい(コンパイラーの場合)....

ポインタまたは関数への参照を問題なく返す場合を除き、代替方法は関数のコピーを返すことです。次に、関数のコピーがどのように見え、どのように動作し、どのように動作するかを考えます。つまり、まず第一にバイト配列であり、これも許可されていません。また、第二バイトは文字通りコードを返すコードと同等です。..ほとんどすべてのヒューリスティックウイルススキャナはまた、ランタイムシステムまたはコンパイル時に返されるコードの実行可能性を検証する方法がないため、ウイルスだと考えてください。配列を返すことができたとしても、どのように関数を返しますか?配列(スタック上のコピー)を返す際の主な問題は、サイズが不明であるため、スタックから削除する方法がなく、関数にも同じジレンマが存在することです(配列は機械語バイナリコード)。また、そのような方法で関数を返した場合、どのようにしてその関数を回して呼び出しますか?

要約すると、関数のポイントではなく関数を返すという概念は、スタックに配置(コピー)されたマシンコードの未知のサイズの配列であるため、失敗します。これはCやC++が許可するように設計されたものではありません。コードを使用すると、特に引数を渡したい場合に、コードを変更してその関数を呼び出すことができます。

これが理にかなっていることを願っています

0
ydobonebi

実際、Cでは関数を渡すことも返すこともできません。関数のポインタ/アドレスのみを渡す/返すことができます。これは概念的にはかなり近いです。正直に言うと、&*を関数ポインターで省略できるため、関数またはポインターが渡されるかどうかは(静的データが含まれていない限り)気にしないでください。以下は、単純な関数ポインター宣言です。

void (*foo)();

fooは、voidを返し、引数を取らない関数へのポインターです。

C++では、それほど違いはありません。すべての呼び出し可能な作成に対して、Cスタイルの関数ポインターまたは新しい便利なstd::functionオブジェクトを引き続き使用できます。また、C++は lambda式 を追加します。これは、関数型言語のクロージャーに何らかの形で機能するインライン関数です。渡されたすべての関数をグローバルにしないようにすることができます。

そして最後に-関数ポインターまたはstd::functionを返すことはばかげているように見えるかもしれませんが、実際はそうではありません。たとえば、ステートマシンパターンは、(ほとんどの場合)次の状態または呼び出し可能な次の状態オブジェクトを処理する関数へのポインターを返すことに基づいています。

0
bartop

Cの正式な文法は実際には関数を返すことを許可していませんが、すべての意図と目的のために、あなたがやりたいことのように見える関数ポインタをいつでも返すことができます:

  int (*getFunc())(int, int) { … }

文法は、そのような機能がサポートされていないことのより基本的な説明であると言って固執しています。規格の規則は後者の懸念事項です。

文法が何かを達成する方法を提供しない場合、特定のコンテキストフリー言語のセマンティクスまたは標準が言うことは重要ではないと思います。

0
Anzurio