14

前提

次のインターフェイスを提供するCライブラリ(C ++から)を使用しています。

void register_callback(void* f, void* data);
void invoke_callback();

問題

ここで、関数テンプレートをコールバックとして登録する必要がありますが、これにより問題が発生します。次のコードを検討してください。

template <typename T> void my_callback(void* data) { … }

int main() {
    int ft = 42;
    register_callback(reinterpret_cast<void*>(&my_callback<int>), &ft);
    invoke_callback();
}

これにより、次のリンカーエラーが発生します(OSXではg++(GCC)4.5.1を使用しますが、コンパイラバージョン/プラットフォームの他のほとんどの組み合わせで機能します)。

アーキテクチャx86_64の未定義のシンボル:

"void my_callback<int>(void*)", referenced from:  
  _main in ccYLXc5w.o

私はそれを理解できると思います。

最初の「ソリューション」</h2>

これは、テンプレートを明示的にインスタンス化することで簡単に修正できます。

template void my_callback<int>(void* data);

残念ながら、コールバックは関数テンプレート内に登録されているため、これは実際のコードには適用できません。また、この関数が呼び出されるテンプレート引数のセットがわからないため、すべての明示的なインスタンス化を提供できません。それら(私はライブラリをプログラミングしています)。したがって、私の実際のコードは次のようになります。

template <typename T>
void do_register_callback(T& value) {
    register_callback(reinterpret_cast<void*>(my_callback<T>), &value);
    // Other things …
}

int main() {
    int ft = 42;
    do_register_callback(ft);
    invoke_callback();
}

2番目の「ソリューション」</h2>

関数テンプレートは、関数を呼び出すことによって暗黙的にインスタンス化されます。それではそれを実行しましょう。ただし、呼び出しが実際に実行されていないことを確認してください(関数には副作用があります)。

template <typename T>
void do_register_callback(T& value) {
    if (false) { my_callback<T>(0); }
    register_callback(reinterpret_cast<void*>(my_callback<T>), &value);
}

これ、最適化が有効になっている場合でも機能するようです(コンパイラーによってデッドブランチが削除されるように)。しかし、これがいつか故障しないかどうかはわかりません。また、これは非常に醜い解決策であり、将来のメンテナがこの明らかに不要なコードを削除しないように、長さの説明コメントが必要です。

質問

テンプレート引数がわからないテンプレートをインスタンス化するにはどうすればよいですか?この質問は明らかにナンセンスです:私はできません。–しかし、これを回避するための卑劣な方法はありますか?

それを除けば、私の回避策は成功することが保証されていますか?

ボーナス質問

このコード(具体的には、関数ポインターをにキャストしたという事実void*)でも、次の警告が生成されます。

ISO C ++は、関数へのポインタとオブジェクトへのポインタ間のキャストを禁止しています

でコンパイルするとき-pedanticライブラリ用に強く型付けされたCラッパーを記述せずに(私の状況では不可能です)、どういうわけか警告を取り除くことができますか?

ideoneでコードを実行する(コンパイルするためのキャストが追加されています)

4

3 に答える 3

8

どうやら、本当の問題はstatic_cast私の元のコードに欠けていることでした:

register_callback(reinterpret_cast<void*>(&my_callback<int>), &ft);

これは正常にコンパイルされますが、GCC4.5を使用すると同様のエラーが発生します。GCC 4.2を使用している場合でもコンパイルされず、代わりに次のコンパイルエラーが発生します。

タイプを判別するにはコンテキスト情報が不十分です

この「コンテキスト情報」が提供されると、コードはコンパイルされ、リンクされます。

register_callback(reinterpret_cast<void*>(
    static_cast<void(*)(void*)>(my_callback<int>)), &value);

キャストが実際に必要かどうか、また(必要な場合)GCC 4.5でキャストを省略できるのに、テンプレートのインスタンス化に失敗する理由がわかりません。しかし、少なくとも私はハックに頼ることなくコンパイルするコードを手に入れました。

于 2011-07-18T15:01:52.673 に答える
5

これは機能するはずです:

template <typename T>
void do_register_callback(T& value) {
   void (*callback)(void*) = my_callback<T>;
   register_callback(reinterpret_cast<void*>(callback), &value);
}

最初の行は、コンパイラにその関数をインスタンス化してアドレスを生成するように強制します。これにより、アドレスを渡すことができます。

編集:このミックスに別のオプションを入れさせてください。クラステンプレートmy_callbackの静的メンバーを作成します-次のようになります。

template <typename T>
struct foo
{
static void my_callback(void* data) {
    T& x = *static_cast<T*>(data);
    std:: cout << "Call[T] with " << x << std::endl;
}
};

さて、あなたのレジスターの人では、あなたは「キャスト」さえ必要としません。

template <typename T>
void do_register_callback(T& value) {
   register_callback(reinterpret_cast<void*>(&foo<int>::my_callback), &value);
}

クラステンプレートをインスタンス化するためのルールは、関数テンプレートとは異なるようです。つまり、クラスのメンバーのアドレスを取得するために、タイプインスタンス化されます。

于 2011-07-18T14:44:47.893 に答える
4

POSIXでは、関数ポインター型とオブジェクトポインター型(C99では定義されていません)の間でキャストするために、次の方法をお勧めします。

typedef void function_type(void*);
function_type *p_to_function = &my_callback<T>;
void* p_to_data = *(void**)&p_to_function;

// Undefined:
// void* p_to_data = (void*)p_to_function;

C ++ランドでは、これはreinterpret_cast<void**>(&p_to_function)fromを実行することに注意してくださいfunction_type**。これは未定義ではなく、とは異なり、実装定義reinterpret_cast<void*>(p_to_function)です。したがって、実装に依存するC++準拠のコードを作成するのがおそらく最善の策です。

于 2011-07-18T15:25:23.683 に答える