12

std::function オブジェクトをパラメーターとして受け入れるオーバーロードされたコンストラクターを使用してクラスを作成しようとしていますが、もちろん、すべての気になることは、任意の署名の std::function に暗黙的にキャストできます。当然のことながら、これは本当に役に立ちます。

例:

class Foo {
  Foo( std::function<void()> fn ) {
    ...code...
  }
  Foo( std::function<int()> fn ) {
    ...code...
  }
};

Foo a( []() -> void { return; } );    // Calls first constructor
Foo b( []() -> int { return 1; } );   // Calls second constructor

これはコンパイルされず、両方のコンストラクターが本質的に同一であり、あいまいであると不平を言います。もちろん、これはナンセンスです。enable_if、is_same、および他の多くのことを試しました。関数ポインターを受け入れることは、ステートフルなラムダの受け渡しを妨げるため、問題外です。確かにこれを達成する方法があるはずですか?

私のテンプレート作成スキルが少し不足しています。通常のテンプレート クラスと関数は十分に簡単ですが、コンパイラでばかげたことをするのは、私にはちょっと無理です。誰か助けてくれませんか?

この質問のバリエーションが以前に尋ねられたことは知っていますが、それらは通常、コンストラクターではなく通常の関数に焦点を当てています。または、戻り値の型ではなく引数によるオーバーロード。

4

3 に答える 3

2

したがって、これにアプローチするには多くの方法があり、さまざまな量の作業が必要です。それらのどれも完全に自明ではありません。

T::operator()最初に、渡された型のシグネチャが type であるかどうかを調べたりチェックしたりすることで、そのシグネチャをアンパックできますR (*)(Args...)

次に、署名の同等性を確認します。

2 番目のアプローチは、通話の互換性を検出することです。次のような特性クラスを記述します。

template<typename Signature, typename F>
struct call_compatible;

これは、std::true_typeまたはが戻り値に変換可能std::false_typeかどうかに依存します。この場合、これで問題は解決します。decltype<F&>()( declval<Args>()... )Signature

ここで、オーバーロードしている 2 つの署名に互換性がある場合は、さらに作業を行う必要があります。std::function<void(double)>つまり、 andがある場合を想像してみてくださいstd::function<void(int)>-- それらはクロスコール互換です。

どれが「最良」のものかを判断するには、私の以前の質問を参照してください。ここでは、多数の署名を取得して、どれが最も一致するかを見つけることができます。次に、戻り値の型チェックを行います。これは複雑になっています!

call_compatibleこのソリューションを使用すると、最終的には次のようになります。

template<size_t>
struct unique_type { enum class type {}; };
template<bool b, size_t n=0>
using EnableIf = typename std::enable_if<b, typename unique_type<n>::type>::type;

class Foo {
  template<typename Lambda, EnableIf<
    call_compatible<void(), Lambda>::value
    , 0
  >...>
  Foo( Lambda&& f ) {
    std::function<void()> fn = f;
    // code
  }
  template<typename Lambda, EnableIf<
    call_compatible<int(), Lambda>::value
    , 1
  >...>
  Foo( Lambda&& f ) {
    std::function<int()> fn = f;
    // code
  }
};

これは、他のソリューションの一般的なパターンです。

これが への最初の刺し傷call_compatibleです:

template<typename Sig, typename F, typename=void>
struct call_compatible:std::false_type {};

template<typename R, typename...Args, typename F>
struct call_compatible<R(Args...), F, typename std::enable_if<
  std::is_convertible<
    decltype(
      std::declval<F&>()( std::declval<Args>()... )
    )
    , R
  >::value
>::type>:std::true_type {};

それはまだテスト/コンパイルされていません。

于 2013-05-11T00:59:24.120 に答える
0

だから私は、MSVC 2013で動作し、(へのポインタを見るようにoperator())うまくいかない、この問題に対する完全に新しい解決策を持っています。

タグで発送します。

任意のタイプを運ぶことができるタグ:

template<class T> struct tag{using type=T;};

型式を受け取り、型タグを生成する関数:

template<class CallExpr>
tag<typename std::result_of<CallExpr>::type>
result_of_f( tag<CallExpr>={} ) { return {}; }

そして使用:

class Foo {
private:
  Foo( std::function<void()> fn, tag<void> ) {
    ...code...
  }
  Foo( std::function<int()> fn, tag<int> ) {
    ...code...
  }
public:
  template<class F>
  Foo( F&& f ):Foo( std::forward<F>(f), result_of_f<F&()>() ) {}
};

コンテキストに応じてまたはコンストラクターFoo(something)のいずれかに転送されるようになりました。std::function<int()>std::function<void()>

tag<>必要に応じて、ctor を追加して変換をサポートすることで、よりスマートにすることができます。次に、戻る関数は次の場所doubleにディスパッチしますtag<int>:

template<class T> struct tag{
  using type=T;
  tag(tag const&)=default;
  tag()=default;
  template<class U,
    class=typename std::enable_if<std::is_convertible<U,T>::value>::type
  >
  tag( tag<U> ){}
};

これは SFINAE のような -construction failure をサポートしていないことに注意してくださいFoo。つまりint、それにパスすると、ソフト障害ではなくハード障害が発生します。

これは VS2012 では直接機能しませんが、コンストラクターの本体で初期化関数に転送できます。

于 2015-07-27T20:37:31.667 に答える