3

std::enable_ifとSFINAEメンバー検出器の組み合わせとして機能するクラスを作成できるかどうか疑問に思いました。

class foo
{
public:
    int bar;
};

template <class T>
typename enable_if_has_bar<T>::type ReturnBar (const T& value)
{
    return value.bar;
}

だから私はこれをやろうとしました。

class foo
{
public:
    int bar;
};

template <class C, C>
class Check;

template <class T, class Enable = void>
class enable_if_has_bar
{};

template <class T>
class enable_if_has_bar<T, Check <decltype(&T::bar),&T::bar>>
{
public:
    typedef decltype(static_cast<T*>(0)->*static_cast<decltype(&T::bar)>(0)) type;
};

template <class T>
typename enable_if_has_bar<T>::type ReturnBar (const T& value)
{
    return value.bar;
}

int main ()
{
    foo foobar;
    foobar.bar = 42;

    cout << ReturnBar(foobar) << endl;
}

http://ideone.com/WKTfmQ

それはうまくいかないようで、私はSFINAEの美術に私ができるほど精通していません。おそらく誰かがそれを改善/修正することができますか?途方に暮れているから。

4

2 に答える 2

4

私は一般的に、あなたが試みたようにカスタムenable_ifスタイルのタイプを作成することを好みます。なぜなら、コードは、の組み合わせではなく、単一のトレイトタイプで読む方が明確だからですenable_if<some_trait<T>, another_trait<T>>。ただし、この場合、コードにいくつかの問題があり、コードが機能しなくなります。

スペシャenable_if_has_barライゼーションが選択されることはありませんReturnBar。returntypeは、プライマリテンプレートをインスタンス化するだけenable_if_has_bar<foo, void>であり、ネストされたを定義することはありませんtype。スペシャライゼーションがインスタンス化される原因はないため、がT::bar有効な式であるかどうかをチェックするものはありません。

あなたのdecltype(static_cast<T*>(0)->*static_cast<decltype(&T::bar)>(0))表現はあなたがおそらく望むようにはint&ならないでしょう。intこれは、が左辺値とdecltype(foobar.*(&foo::bar))同等でdecltype(foobar.bar)あり、であるためです。したがって、はです。パラメータがconstであるために関数が返されると、関数のコンパイルに失敗するため、non-constにバインドすることはできません。foobar.bardecltypeint&ReturnBarint&valuevalue.barint&

動作するバージョンは次のとおりです。

template <class T>
  class has_bar
  {
    template<typename U, typename = decltype(&U::bar)>
      static std::true_type
      test(U*);

    static std::false_type
    test(...);

  public:
    static const int value = decltype(test((T*)nullptr))::value;
  };

template<typename T, bool = has_bar<T>::value>
  struct enable_if_has_bar
  { };

template<typename T>
  struct enable_if_has_bar<T, true>
  : std::decay<decltype(std::declval<T&>().*(&T::bar))>
  { };

has_barこれは最初に、型にネストされたメンバーがあるかどうかの質問に答えるヘルパーを宣言します。そのヘルパーはSFINAEを使用してtrueまたはfalse値を取得します。&T::barが有効な式の場合、の最初のオーバーロードがtest使用されます。これは戻り値true_typeであるため、 ievalueに設定されます。それ以外の場合、フォールバックオーバーロードが選択され、に設定されます。true_type::valuetruevaluefalse

次に、enable_if_has_barテンプレートは、の値として推定されるデフォルトのテンプレートパラメータを使用しますhas_bar<T>::value。プライマリテンプレートは、has_bar<T>::valueがfalseの場合に使用されます。特殊化はhas_bar<T>::valuetrueの場合に使用されます。この場合、式&T::barが有効であることがわかり、それをdecltype式で使用して型を取得できます。

std::decayint&decltype式の結果をちょうどに変換するために使用されintます。fromの継承decayは、メンバーを定義するために使用するよりも少し短くなります。これは次のようになります。

typedef typename std::decay<decltype(std::declval<T&>().*(&T::bar))>::type type;

未評価の式で標準ユーティリティを使用しdeclval<T>()ました。これは、入力するのが少し短く、。よりも慣用的で表現力がありstatic_cast<T*>(0)ます。

使用する代わりに、タイプからタイプを取得decayするための別のヘルパータイプがあります。intint T::*

template<typename T>
  struct remove_class;
  { };

template<typename Member, typename Class>
  struct remove_class<Member Class::*>
  {
    typedef Member type;
  };

template<typename T>
  struct enable_if_has_bar<T, true>
  : remove_class<decltype(&T::bar)>
  { };

(名前remove_classはあまり良くありませんが、基本的にはデータメンバーへのポインタ型を取り、メンバーの型を示します。)

于 2012-12-31T00:13:32.233 に答える
1

Jonathan Wakelyの答えは非常に良いですが、少し異なるパターンを提供させてください。

//this goes in some header so you can use it everywhere
template<typename T>
struct TypeSink{
    using Type = void;
};
template<typename T>
using TypeSinkT = typename TypeSink<T>::Type;

//here is the use case
template<typename T, typename = void>
struct enable_if_has_bar{ };

template<typename T>
struct enable_if_has_bar<T, TypeSinkT<decltype(std::declval<T&>().*(&T::bar))>>
  : std::decay<decltype(std::declval<T&>().*(&T::bar))>
  { };

これが実際の例です:http://ideone.com/un0ZgH

当然、好みの問題ですが、SFINAEテストの構文とメタ関数本体で実際に使用している構文はまったく同じであり、隣り合っているため、タイプシンクパターンを使用するのが好きです。したがって、バグが発生しにくいことを願っています。

于 2014-08-11T11:22:39.420 に答える