30

テンプレートメタプログラミングでは、リターンタイプでSFINAEを使用して、特定のテンプレートメンバー関数を選択できます。

template<int N> struct A {
  int sum() const noexcept
  { return _sum<N-1>(); }
private:
  int _data[N];
  template<int I> typename std::enable_if< I,int>::type _sum() const noexcept
  { return _sum<I-1>() + _data[I]; }
  template<int I> typename std::enable_if<!I,int>::type _sum() const noexcept
  { return _data[I]; }
};

ただし、これはコンストラクターでは機能しません。コンストラクターを宣言したいとします

template<int N> struct A {
   /* ... */
   template<int otherN>
   explicit(A<otherN> const&); // only sensible if otherN >= N
};

ただし、許可しないでotherN < Nください。

では、ここでSFINAEを使用できますか?テンプレートパラメータの自動推定を可能にするソリューションにのみ興味があるので、

A<4> a4{};
A<5> a5{};
A<6> a6{a4};  // doesn't compile
A<3> a3{a5};  // compiles and automatically finds the correct constructor

注:これは非常に単純化された例であり、SFINAEはやり過ぎでstatic_assert十分な場合があります。ただし、代わりにSFINAEを使用できるかどうかを知りたいです。

4

5 に答える 5

30

デフォルトの型引数をテンプレートに追加できます。

template <int otherN, typename = typename std::enable_if<otherN >= N>::type>
explicit A(A<otherN> const &);
于 2013-01-30T11:46:46.083 に答える
17

SFINAEをトリガーする方法はたくさんありますがenable_if、そのうちの1つにすぎません。初めに:

ワットはstd::enable_if?

それはこれだけです:

template<bool, class T=void> enable_if{ typedef T type; };
template<class T> enable_if<false,T> {};
template<bool b, class T=void> using enable_if_t = typename enable_f<b,T>::type;

アイデアはtypename enable_if<false>::typeエラーにすることです。したがって、それを含むテンプレート宣言はすべてスキップします。

では、これはどのようにして関数の選択をトリガーできるのでしょうか?

機能の無効化

アイデアは、ある部分で宣言を誤ったものにしている:

返品タイプ別

template<class Type>
std::enable_if_t<cond<Type>::value,Return_type> function(Type);

実際のパラメータによる

template<class Type>
return_type function(Type param, std::enable_if_t<cond<Type>::value,int> =0) 

テンプレートパラメータによる

template<class Type, 
    std::enable_if_t<cond<Type>::value,int> =0> //note the space between > and =
return_type function(Type param) 

機能の選択

次のようなトリックを使用して、さまざまな代替案をパラメータ化できます。

tempplate<int N> struct ord: ord<N-1>{};
struct ord<0> {};

template<class T, std::enable_if<condition3, int> =0>
retval func(ord<3>, T param) { ... }

template<class T, std::enable_if<condition2, int> =0>
retval func(ord<2>, T param) { ... }

template<class T, std::enable_if<condition1, int> =0>
retval func(ord<1>, T param) { ... }

template<class T> // default one
retval func(ord<0>, T param) { ... }

// THIS WILL BE THE FUCNTION YOU'LL CALL
template<class T>
retval func(T param) { return func(ord<9>{},param); } //any "more than 3 value"

これは、満たされている場合、どれよりも1番目/2番目/3番目/4番目の関数を呼び出します。condition3condition2condition1

その他のSFINAEトリガー

コンパイル時の条件の記述は、明示的な特殊化の問題か、評価されていない式の成功/失敗の問題のいずれかです。

例えば:

template<class T, class = void>
struct is_vector: std::false_type {};
template<class X>
struct is_vector<vector<X> >:: std::true_type {};

ですis_vector<int>::valuefalseis_vecttor<vector<int> >::value_true

または、内省によって、

template<class T>
struct is_container<class T, class = void>: std::false_type {};

template<class T>
struct is_container<T, decltype(
  std::begin(std::declval<T>()),
  std::end(std::declval<T>()),
  std::size(std::declval<T>()),
  void(0))>: std::true_type {};

与えられれば、コンパイルすることができis_container<X>::valueます。trueX xstd::begin(x)

秘訣は、すべての部分式がコンパイル可能である場合にのみ、decltype(...)実際にはvoid(演算子は前の式を破棄する)ということです。,


他にも多くの選択肢があります。このすべての間にあなたが何か役に立つものを見つけることができることを願っています。

于 2018-05-28T08:33:44.650 に答える
8

C ++ 11では、デフォルトのテンプレートパラメータを使用できます。

template <int otherN, class = typename std::enable_if<otherN >= N>::type>
explicit A(A<otherN> const &);

ただし、コンパイラがデフォルトのテンプレートパラメータをまだサポートしていない場合、または複数のオーバーロードが必要な場合は、次のようなデフォルトの関数パラメータを使用できます。

template <int otherN>
explicit A(A<otherN> const &, typename std::enable_if<otherN >= N>::type* = 0);
于 2013-01-30T23:09:15.013 に答える
8

受け入れられた答えはほとんどの場合に適していますが、条件が異なる2つのそのようなコンストラクターのオーバーロードが存在する場合は失敗します。その場合も解決策を探しています。

はい:受け入れられたソリューションは機能しますが、たとえば、

template <int otherN, typename = typename std::enable_if<otherN == 1>::type>
explicit A(A<otherN> const &);

template <int otherN, typename = typename std::enable_if<otherN != 1>::type>
explicit A(A<otherN> const &);

なぜなら、このページで述べられているように、

よくある間違いは、デフォルトのテンプレート引数のみが異なる2つの関数テンプレートを宣言することです。デフォルトのテンプレート引数は関数テンプレートの署名の一部ではなく、同じ署名を持つ2つの異なる関数テンプレートを宣言することは違法であるため、これは違法です。

同じページで提案されているように、次のように、SFINAEを適用し、署名を変更して、値のタイプ(タイプではない)テンプレートパラメーターにこの問題を回避できます。

template <int otherN, typename std::enable_if<otherN == 1, bool>::type = true>
explicit A(A<otherN> const &);

template <int otherN, typename std::enable_if<otherN != 1, bool>::type = true>
explicit A(A<otherN> const &);
于 2019-04-06T21:19:59.017 に答える
1

requiresC ++ 20では、キーワードを使用できます

C ++ 20を使用すると、SFINAEを取り除くことができます。

キーワードは!のrequires単純な代替です。enable_if

otherN == Nの場合は、デフォルトのcopy ctor に該当するため、特殊なケースであることに注意してください。そのため、これを処理する場合は、個別に実装する必要があります。

template<int N> struct A {
   A() {}    

   // handle the case of otherN == N with copy ctor
   explicit A(A<N> const& other) { /* ... */ }

   // handle the case of otherN > N, see the requires below
   template<int otherN> requires (otherN > N)
   explicit A(A<otherN> const& other) { /* ... */ }

   // handle the case of otherN < N, can add requires or not
   template<int otherN>
   explicit A(A<otherN> const& other) { /* ... */ }
};

このrequires句は、過負荷解決でこのメソッドを検討するかどうかconstant expressionを評価trueまたはfalse決定するaを取得します。requires句がtrueの場合、このメソッドは、requires句がない別のメソッドよりも特殊化されているため、優先されます。

コード:https ://godbolt.org/z/RD6pcE

于 2020-04-09T20:27:11.863 に答える