web-dev-qa-db-ja.com

異なるenable_if条件を使用したメンバー関数の選択

クラステンプレートパラメータに基づいて、メンバー関数のどのバージョンが呼び出されるかを判断しようとしています。私はこれを試しました:

#include <iostream>
#include <type_traits>

template<typename T>
struct Point
{
  void MyFunction(typename std::enable_if<std::is_same<T, int>::value, T >::type* = 0)
  {
    std::cout << "T is int." << std::endl;
  }

  void MyFunction(typename std::enable_if<!std::is_same<T, int>::value, float >::type* = 0)
  {
    std::cout << "T is not int." << std::endl;
  }
};

int main()
{
  Point<int> intPoint;
  intPoint.MyFunction();

  Point<float> floatPoint;
  floatPoint.MyFunction();
}

これは、「Tがintの場合は最初のMyFunctionを使用し、Tがintでない場合は2番目のMyFunctionを使用する」と思っていましたが、「エラー:「structstd :: enable_if」に「type」という名前の型がありません」というコンパイラエラーが発生します。 。私がここで間違っていることを誰かが指摘できますか?

18
David Doria

enable_ifが機能するのは、 テンプレート引数の置換によってエラーが発生した であり、そのため、置換はオーバーロード解決セットから削除され、他の実行可能なオーバーロードのみがコンパイラによって考慮されます。

あなたの例では、テンプレート引数Tはその時点ですでにわかっているため、メンバー関数をインスタンス化するときに置換は発生しません。試行していることを実現する最も簡単な方法は、デフォルトでTに設定されているダミーのテンプレート引数を作成し、それを使用してSFINAEを実行することです。

template<typename T>
struct Point
{
  template<typename U = T>
  typename std::enable_if<std::is_same<U, int>::value>::type
    MyFunction()
  {
    std::cout << "T is int." << std::endl;
  }

  template<typename U = T>
  typename std::enable_if<std::is_same<U, float>::value>::type
    MyFunction()
  {
    std::cout << "T is not int." << std::endl;
  }
};

編集:

HostileForkがコメントで言及しているように、元の例では、ユーザーがメンバー関数のテンプレート引数を明示的に指定して、誤った結果を取得する可能性があります。以下は、メンバー関数の明示的な特殊化がコンパイルされないようにする必要があります。

template<typename T>
struct Point
{
  template<typename... Dummy, typename U = T>
  typename std::enable_if<std::is_same<U, int>::value>::type
    MyFunction()
  {
    static_assert(sizeof...(Dummy)==0, "Do not specify template arguments!");
    std::cout << "T is int." << std::endl;
  }

  template<typename... Dummy, typename U = T>
  typename std::enable_if<std::is_same<U, float>::value>::type
    MyFunction()
  {
    static_assert(sizeof...(Dummy)==0, "Do not specify template arguments!");
    std::cout << "T is not int." << std::endl;
  }
};
15
Praetorian

簡単な解決策は、ワーカーへの委任を使用することですprivate関数:

_template<typename T>
struct Point
{

  void MyFunction()
  {
     worker(static_cast<T*>(nullptr)); //pass null argument of type T*
  }

private:

  void worker(int*)
  {
    std::cout << "T is int." << std::endl;
  }

  template<typename U>
  void worker(U*)
  {
    std::cout << "T is not int." << std::endl;
  }
};
_

Tintの場合、static_cast<T*>(0)のタイプが_int*_であることが判明したため、最初のworker関数が呼び出されます。それ以外の場合はすべて、workerのテンプレートバージョンが呼び出されます。

4
Nawaz

Praetorianの提案に基づくと(ただし、関数の戻り値の型を変更せずに)、これは機能するようです。

#include <iostream>
#include <type_traits>

template<typename T>
struct Point
{
  template<typename U = T>
  void MyFunction(typename std::enable_if<std::is_same<U, int>::value, U >::type* = 0)
  {
    std::cout << "T is int." << std::endl;
  }

  template<typename U = T>
  void MyFunction(typename std::enable_if<!std::is_same<U, int>::value, float >::type* = 0)
  {
    std::cout << "T is not int." << std::endl;
  }
};

int main()
{
  Point<int> intPoint;
  intPoint.MyFunction();

  Point<float> floatPoint;
  floatPoint.MyFunction();
}
1
David Doria

enable_if推定関数テンプレート引数または特殊なクラステンプレート引数に対してのみ機能します。明らかに固定されたT = intを使用すると、2番目の宣言が誤っているため、実行していることは機能しません。

これはそれができる方法です:

template <typename T>
void MyFreeFunction(Point<T> const & p,
                    typename std::enable_if<std::is_same<T, int>::value>::type * = nullptr)
{
    std::cout << "T is int" << std::endl;
}

// etc.

int main()
{
    Point<int> ip;
    MyFreeFunction(ip);
}

別の方法は、PointをさまざまなタイプのTに特化するか、上記の無料の関数をネストされたメンバーテンプレートラッパーに配置することです(これはおそらくより「適切な」ソリューションです)。

1
Kerrek SB

以下のポイントテンプレートは、テンプレート引数Tとしてintまたはfloatを使用してのみインスタンス化できます。

質問に答えるには:ここでworker()は、method()呼び出しのテンプレートパラメーターに正確に応じて呼び出されますが、それでもタイプを制御できます。

    template<typename T>
    struct Point
    {
        static_assert (
              std::is_same<T, int>()  ||
              std::is_same<T, float>()
            );

        template<typename U>
        void method(U x_, U y_)
        {
            if constexpr (std::is_same<T, U>()) {
                worker(x_, y_);
                return;
            }
            // else 
            worker(
                static_cast<T>(x_),
                static_cast<T>(y_)
            );
            return ;
        }


    private:

        mutable T x{}, y{};

        void worker(T x_, T y_)
        {
            // nothing but T x, T y
        }

    };

上記のworker()は、静的として宣言されている場合でももちろん機能します。いくつかの正当な理由があります。上記の他のいくつかの拡張が可能(そして単純)ですが、答えに固執しましょう。

0
user5560811