2012-06-02 43 views
5

は、私はいくつかの副作用を実行し、その答えを返す関数があるとします。関数ポインタと型変換を返し

int foo() 
{ 
    perform_some_side_effect(); 
    return 42; 
} 

私は関数ポインタにfooをバインドしたいが、私が興味を持っていませんよ答えは、ちょうど副作用は:

error: invalid conversion from ‘int (*)()’ to ‘void (*)()’ 

void (*bar)() = foo; 

しかし、これは型エラーのように見えます

そのエラーの背後にある根拠は何ですか?なぜ型システムは私に答えを無視できないのですか?サイドノートでは


私はstd::functionに関数ポインタをラップする場合、それが動作します:

std::function<void()> baz = foo; 

std::function(明らかに)型システムでは、この制限を回避するために管理しない方法は?

+1

を動作させるためにいくつかの策略をとります。 'reinterpret_cast'を使ってこれを(あなたの危険で)無効にすることができます。 –

+0

@StevenBurnap:質問は*なぜ*変換できないのですか?彼らが異なるタイプであることは明らかです。 'float'は' int'ではありませんが、あなたはそれらを変換することができます。 – Puppy

+0

私はそれが一般的な規則として暗黙的にポインタをキャストすることが許可されていないためだと思います。 –

答えて

9

なぜなら、種類が異なり、コール先(関数ポインタを介して)で生成されたコードが異なるからです。すべての引数がスタックに書き込まれ、戻り値のためのスペースもスタックに予約されている呼び出し規約を考えてみましょう。呼び出しがvoid (*)()を通過した場合、戻り値のためにスタックにスペースは予約されませんが、関数は呼び出し元がスペースを確保しておくべき場所に42を書き込みます。

How does std::function (apparently) manage to circumvent this restriction in the type system?

これはありません。実際の関数に呼び出しをラップする関数オブジェクトを作成します。コンパイラが処理するときに

void operator()() const { 
    foo(); 
} 

fooへの呼び出しは、それがコールintを返す関数を行う必要があります知っている、それは呼び出し規約に従って、そうします:それはのようなメンバーが含まれています。テンプレートは返されないので、実際に返された値は無視されます。

+1

+1:最も完全な答え。 –

1

std::functionはソースとの互換性のみが必要です。つまり、結果を無視する新しいキャリングコードを生成する新しいクラスを生成できます。関数ポインタはバイナリ互換でなければならず、そのジョブは実行できません。void(*)()int(*)()は全く同じコードを指しています。

1

あなたはあなたの特定のケースのためにこれをやってstd::function<>と考えることができます:

void __func_void() 
{ 
    foo(); 
} 

それは実際にはそれよりも少し複雑ですが、ポイントは、それがないように型消去と一緒にテンプレートコードを生成していることです詳細については気をつけてください。

1

他の人が言っていることに加えて、呼び出し側は結果に対してどのデストラクタを呼び出すべきかを知るために戻り値の型も必要です(戻り値は一時的なものかもしれません)。


GCCとクランはこれを受け入れるが、残念ながらそれは同様に簡単

として
auto (*bar)() = foo; 

ではありません。私はそれが実際に正しいかどうかを確認するために仕様を再確認する必要があります。

更新:仕様は

The auto type-specifier signifies that the type of a variable being declared shall be deduced from its initializer or that a function declarator shall include a trailing-return-type.

を言います。これは、高速で読んだときに誤解を招くことができますが、これは唯一のトップレベルの宣言子に適用するGCCと打ち鳴らすことによって実現されます。私たちの場合、これはポインタ宣言子です。それにネストされた宣言子は関数宣言子です。だから、voidの代わりにautoを置き換え、コンパイラがあなたのために型を推論します。ところで


、いつでも手動でこの作業を行うことができますが、それは、それが合理的で、彼らはさまざまな種類あるだけのことである

template<typename FunctionType> 
struct Params; 

template<typename ...Params> 
struct Params<void(Params...)> { 
    template<typename T> 
    using Identity = T; 

    template<typename R> 
    static Identity<R(Params...)> *get(R f(Params...)) { 
    return f; 
    } 
}; 

// now it's easy 
auto bar = Params<void()>::get(foo); 
+0

これでもVS2011 RCでコンパイルされます。自動バー=テスト; \t bar(); – Jagannath

関連する問題