174

Scott Meyersは、彼の次の本 EC++11 の内容とステータスを投稿しました。彼は、本の 1 つの項目が「関数シグネチャで回避する」std::enable_ifである可能性があると書いています。

std::enable_if関数の引数、戻り値の型、またはクラス テンプレートまたは関数テンプレート パラメーターとして使用して、関数またはクラスをオーバーロードの解決から条件付きで削除できます。

この質問では、3 つのソリューションすべてが示されています。

関数パラメータとして:

template<typename T>
struct Check1
{
   template<typename U = T>
   U read(typename std::enable_if<
          std::is_same<U, int>::value >::type* = 0) { return 42; }

   template<typename U = T>
   U read(typename std::enable_if<
          std::is_same<U, double>::value >::type* = 0) { return 3.14; }   
};

テンプレート パラメータとして:

template<typename T>
struct Check2
{
   template<typename U = T, typename std::enable_if<
            std::is_same<U, int>::value, int>::type = 0>
   U read() { return 42; }

   template<typename U = T, typename std::enable_if<
            std::is_same<U, double>::value, int>::type = 0>
   U read() { return 3.14; }   
};

戻り型として:

template<typename T>
struct Check3
{
   template<typename U = T>
   typename std::enable_if<std::is_same<U, int>::value, U>::type read() {
      return 42;
   }

   template<typename U = T>
   typename std::enable_if<std::is_same<U, double>::value, U>::type read() {
      return 3.14;
   }   
};
  • どのソリューションを優先する必要があり、他のソリューションを避ける必要があるのはなぜですか?
  • 「関数シグネチャでの回避std::enable_if」が戻り値の型 (通常の関数シグネチャの一部ではなく、テンプレートの特殊化の一部である) としての使用に関係するのはどの場合ですか?
  • メンバー関数テンプレートと非メンバー関数テンプレートに違いはありますか?
4

4 に答える 4

114

ハックをテンプレート パラメーターに入れます

テンプレート パラメーターのアプローチには、他のenable_ifアプローチよりも少なくとも 2 つの利点があります。

  • 可読性: enable_if の使用と戻り値/引数の型が、型名の曖昧さ回避とネストされた型アクセスの 1 つの乱雑なチャンクにまとめられることはありません。曖昧さ回避ツールとネストされた型の煩雑さはエイリアス テンプレートで軽減できますが、それでも 2 つの無関係なものが一緒にマージされます。enable_if の使用は、戻り値の型ではなく、テンプレート パラメーターに関連しています。それらをテンプレート パラメーターに含めることは、重要なことにより近いことを意味します。

  • 普遍的な適用性: コンストラクターには戻り値の型がなく、一部の演算子には追加の引数を指定できないため、他の 2 つのオプションはどこにも適用できません。テンプレート パラメータに enable_if を入れると、テンプレートでしか SFINAE を使用できないため、どこでも機能します。

私にとって、読みやすさの側面は、この選択の大きな動機となる要因です。

于 2013-01-31T10:42:28.443 に答える
58

std::enable_ifは、テンプレート引数の推論中の「置換の失敗はエラーではない」(別名 SFINAE) の原則に依存しています。これは非常に脆弱な言語機能であり、正しく行うには細心の注意を払う必要があります。

  1. 内の条件にネストされたテンプレートまたは型定義が含まれている場合 (ヒント:トークンenable_ifを探してください)、これらのネストされたテンプレートまたは型の解決は、通常、推定されないコンテキストです。このような非推定コンテキストでの代入の失敗はエラーです。::
  2. オーバーロードの解決があいまいになるため、複数のオーバーロードのさまざまな条件がenable_if重複することはありません。これは、作成者として自分で確認する必要があるものですが、適切なコンパイラ警告が表示されます。
  3. enable_ifオーバーロードの解決中に実行可能な関数のセットを操作します。これは、他のスコープから (たとえば、ADL を介して) 持ち込まれた他の関数の存在に応じて驚くべき相互作用をする可能性があります。これにより、あまり堅牢ではありません。

つまり、機能する場合は機能しますが、機能しない場合はデバッグが非常に困難になる可能性があります。非常に良い代替手段は、タグ ディスパッチを使用することですdetail。つまり、enable_if.

template<typename T>
T fun(T arg) 
{ 
    return detail::fun(arg, typename some_template_trait<T>::type() ); 
}

namespace detail {
    template<typename T>
    fun(T arg, std::false_type /* dummy */) { }

    template<typename T>
    fun(T arg, std::true_type /* dummy */) {}
}

タグのディスパッチはオーバーロード セットを操作しませんが、コンパイル時の式 (型特性など) を介して適切な引数を提供することで、必要な関数を正確に選択するのに役立ちます。私の経験では、これはデバッグして正しくするのがはるかに簡単です。あなたが洗練された型特性の意欲的なライブラリ作成者である場合、enable_if何らかの形で必要になるかもしれませんが、コンパイル時の条件のほとんどの通常の使用では推奨されません。

于 2013-01-30T09:33:52.313 に答える