web-dev-qa-db-ja.com

異なるプロトタイプの関数ポインターの配列を作成するにはどうすればよいですか?

このように定義された関数がいくつかあります。

_ParentClass*    fun1();
ParentClass*    fun2();
ParentClass*    fun3(bool inp=false);
ChildClass*     fun4();
ChildClass*     fun5(int a=1, int b=3);
_

次のように、それらをある種の配列に入れたいと思います。

_void* (*arr[5])() = {
    (void* (*)())fun1,
    (void* (*)())fun2,
    (void* (*)())fun3,
    (void* (*)())fun4,
    (void* (*)())fun5
}
_

この関数の配列を次のように単純に使用したいと思います

_for(int i=0; i<5; i++)
    someFunction(arr[i]());
_

ここで、問題はvoid* (*arr[5])()であることがわかりましたが、引数を指定せずに関数のみを使用したい場合、これらすべてを同じ配列の一部にしたいと思います。

ただし、これらは非常にCスタイルの方法です。 C++のテンプレートを使用してそれを行うより良い方法はありますか?

28
vc669

Cスタイルであるかどうかにかかわらず、あなたが持っているのはまっすぐな未定義の振る舞いです。ラムダを使用します。

_void (*arr[5])() = {
    [] { fun1(); },
    [] { fun2(); },
    [] { fun3(); },
    [] { fun4(); },
    [] { fun5(); }
};
_

これらは、関数の正しい型を介して呼び出しを実行し、それ自体がvoid (*)()に変換可能であるため、問題ありません。

ラムダは変換のコンテキストを提供するため、戻り値の転送は十分に単純なままです。あなたの場合、ChildClassはおそらくParentClassから継承するため、暗黙的な変換で十分です。

_ParentClass *(*arr[5])() = {
    []() -> ParentClass * { return fun1(); },
    []() -> ParentClass * { return fun2(); },
    []() -> ParentClass * { return fun3(); },
    []() -> ParentClass * { return fun4(); },
    []() -> ParentClass * { return fun5(); }
};
_
42
Quentin

ただし、引数を指定せずに関数のみを使用したい場合

それは単にそのようには動作しません。関数の宣言をヘッダーに配置するときに、デフォルトのパラメーターをヘッダーに書き込む必要があり、実装ソースファイルの定義に配置できないのはなぜだろうと思ったことはありませんか?

これは、デフォルトのパラメーターが実際には関数に「埋め込まれて」いないためです。しかし、コンパイラーは、これらのパラメーターが省略されている呼び出し位置で、これらのパラメーターで関数呼び出しを補強するために使用します編集ヘッダー、ergo関数宣言、変更を実際に有効にするため!)

そのような関数ポインターの配列を構築するために、本当に奇妙な型キャストの狂気を行うことは完全に可能ですが、未定義の動作を呼び出さないためには、最終的に元の関数呼び出しシグネチャにキャストする必要があります。

関数ポインタをバインドする必要がある場合は、呼び出しを抽象化する何らかのタイプのデフォルトパラメータのセットとともに、パラメータを提供し、外部にポリモーフィックインターフェイスを提供します。したがって、関数バインダーには関数を呼び出すoperator()があるstd::vector<function_binder>またはfunction_binder[]があります。

しかし、そもそもバインドを行うときは、匿名関数、つまりラムダでバインドできます。ラムダインスタンス化の時点で、デフォルトのパラメーターがバインドされています。

std::vector<void(*)()> fvec = {
    []{ func0(); },
    []{ func1(); },
    []{ func2(); },
}
16
datenwolf

std::bind

std::function<ParentClass *(void)> arr[5] = {
    std::bind(&fun1),
    std::bind(&fun2),
    std::bind(&fun3, false),
    std::bind(&fun4),
    std::bind(&fun5, 1, 3)
};

今、あなたはできる

for(int i=0; i<5; i++)
    arr[i]();

すべての関数のすべての関数パラメーターがバインドされていることを確認する必要があります。

これは、メンバー関数でもうまく機能します。最初のパラメーターとしてオブジェクト参照(例:this)をバインドするだけです。

9
Detonar

A c ++ 2 ソリューション:

#define RETURNS(...) \
  noexcept(noexcept(__VA_ARGS__)) \
  -> decltype(__VA_ARGS__) \
  { return __VA_ARGS__; }


template<auto f, class R, class...Args>
struct explicit_function_caster {
  using Sig=R(Args...);
  using pSig=Sig*;
  constexpr operator pSig()const {
    return [](Args...args)->R {
      return static_cast<R>(f(std::forward<Args>(args)...));
    };
  }
};

template<auto f>
struct overload_storer_t {
  template<class R, class...Args>
  constexpr (*operator R() const)(Args...) const {
    return explicit_function_caster<f, R, Args...>{};
  }
  template<class...Args>
  auto operator()(Args&&...args)
  RETURNS( f( std::forward<Args>(args)... ) )
};
template<auto f>
overload_storer_t<f> generate_overloads={};

#define OVERLOADS_OF(...) \
  generate_overloads< \
    [](auto&&...args) \
    RETURNS( __VA_ARGS__( decltype(args)(args)... ) ) \
  >

これは定型文の多くですが、私たちを取得します:

ParentClass* (*arr[5])() = {
  OVERLOADS_OF(fun1),
  OVERLOADS_OF(fun2),
  OVERLOADS_OF(fun3),
  OVERLOADS_OF(fun4),
  OVERLOADS_OF(fun5)
};
void (*arr2[5])() = {
  OVERLOADS_OF(fun1),
  OVERLOADS_OF(fun2),
  OVERLOADS_OF(fun3),
  OVERLOADS_OF(fun4),
  OVERLOADS_OF(fun5)
};

基本的に generate_overloads<x>constexpr呼び出し可能オブジェクトxを受け取り、コンパイル時にそれを互換性のある署名の関数へのポインターにキャストし、(ほぼ)任意の署名で呼び出すことができます。

その間、OVERLOADS_OFは、関数名をconstexprオブジェクトに変換し、その関数名の解決をオーバーロードします。ここで使用するのは、fun3は関数ポインタとしてデフォルトの引数を認識しませんが、オーバーロードの解決時には認識します。

この特定のケースでは、この作業を行うために単におもちゃのラムダを書く方がはるかに簡単です。これは、互換性のある任意の署名用にこれらのおもちゃのラムダを自動化するための単なる試みです。

質問にC++ 14でタグ付けしたため、関数ポインターを使用しないでください!

C++ 14では、std::functionおよびラムダを優先する必要があります。

また、Cスタイルの配列は使用せず、std::arrayまたはstd::vectorのみを使用する必要があります。

また、生のポインタを避け、std::unique_ptrstd::shared_ptrを使用します。

それを解決する最も簡単で最良の方法は次のとおりです。

std::array<std::function<ParentClass*()>,5> arr {
    []() { return fun1(); },
    []() { return fun2(); },
    []() { return fun3(true); },
    []() { return fun4(); },
    []() { return fun5(7, 9); }
};

@Quentinの回答のような単純なポインターの配列ではないのはなぜですか?彼はラムダを使用しましたが、何かをバインドするラムダは使用できません(必要な場合)。

1
Marek R