web-dev-qa-db-ja.com

テンプレートクラスの特殊化にSFINAEを使用する

これらの宣言があるとしましょう

template<typename T> class User;
template<typename T> class Data;

User<>T = Data<some_type>を実装したいおよびData<some_type>から派生したクラスだけでなく、他の場所で定義された他の特殊化も許可する.

クラステンプレートUser<>の宣言がまだない場合は、簡単に

template<typename T,
         typename A= typename std::enable_if<is_Data<T>::value>::type>
class User { /*...*/ };

どこ

template<template<typename> data>> struct is_Data
{ static const bool value = /* some magic here (not the question) */; };

ただし、これには2つのテンプレートパラメータがあるため、前の宣言と競合します。ここで、User<>は1つのテンプレートパラメータだけで宣言されています。他に何かできることはありますか?

(注意

template<typename T,
         typename A= typename std::enable_if<is_Data<T>::value>::type>
class User<T> { /*...*/ };

動作しません(デフォルトのテンプレート引数は部分的な特殊化では使用できません)、また動作しません

template<typename T> class User<Data<T>> { /*...*/ };

Data<>から派生した型は許可されないため、

template<typename T>
class User<typename std::enable_if<is_Data<T>::value,T>::type>
{ /*...*/ };

テンプレートパラメータTは部分的な特殊化では使用されないです。)

41
Walter

あなたはまだより良い答えを待っていると言っていたので、これを私の見解とする。それは完璧ではありませんが、SFINAEと部分的な専門化を使用して、可能な限りあなたを得ることができると思います。 (コンセプトは完全でエレガントなソリューションを提供すると思いますが、そのためにはもう少し待たなければなりません。)

このソリューションは、C++ 14の最終バージョンの後の標準作業草案で最近指定されたエイリアステンプレートの機能に依存していますが、しばらくの間、実装によってサポートされてきました。ドラフトN4527 [14.5.7p3]の関連する表現は次のとおりです。

ただし、template-idが依存している場合、後続のテンプレート引数の置換は引き続きtemplate-idに適用されます。 [例:

template<typename...> using void_t = void;
template<typename T> void_t<typename T::foo> f();
f<int>(); // error, int does not have a nested type foo

—例を終了]

このアイデアを実装する完全な例を次に示します。

#include <iostream>
#include <type_traits>
#include <utility>

template<typename> struct User { static void f() { std::cout << "primary\n"; } };

template<typename> struct Data { };
template<typename T, typename U> struct Derived1 : Data<T*> { };
template<typename> struct Derived2 : Data<double> { };
struct DD : Data<int> { };

template<typename T> void take_data(Data<T>&&);

template<typename T, typename = decltype(take_data(std::declval<T>()))> 
using enable_if_data = T;

template<template<typename...> class TT, typename... Ts> 
struct User<enable_if_data<TT<Ts...>>> 
{ 
    static void f() { std::cout << "partial specialization for Data\n"; } 
};

template<typename> struct Other { };
template<typename T> struct User<Other<T>> 
{ 
    static void f() { std::cout << "partial specialization for Other\n"; } 
};

int main()
{
    User<int>::f();
    User<Data<int>>::f();
    User<Derived1<int, long>>::f();
    User<Derived2<char>>::f();
    User<DD>::f();
    User<Other<int>>::f();
}

それを実行すると印刷されます:

primary
partial specialization for Data
partial specialization for Data
partial specialization for Data
primary
partial specialization for Other

ご覧のとおり、しわがあります。DDの部分的な特殊化は選択されていません。宣言した方法により、特殊化は選択できません。だから、なぜ私たちはただ言ってはいけないのですか

template<typename T> struct User<enable_if_data<T>> 

DDにも一致するようにしますか?これは実際にはGCCで機能しますが、[14.5.5p8.3、8.4]のため、ClangとMSVCによって正しく拒否されます([p8.3]は冗長になるため、将来的になくなる可能性があります- CWG 20 ):

  • 特殊化の引数リストは、プライマリテンプレートの暗黙の引数リストと同一であってはなりません。
  • スペシャライゼーションは、プライマリテンプレート(14.5.5.2)よりも特化されている必要があります。

User<enable_if_data<T>>User<T>(上記の最初の引用で説明されているように、個別に処理されるデフォルト引数へのモジュロ置換)と同等であるため、部分的な特殊化の無効な形式です。残念ながら、DDのようなものを一致させるには、一般に、Tという形式の部分的な特殊化引数が必要になります。他の形式がなくても、すべてのケースに一致します。ですから、この部分は与えられた制約の範囲内では解決できないと結論付けることができると思います。 ( Core issue 198 があります。これは、テンプレートエイリアスの使用に関する将来のルールの可能性を示唆していますが、私たちのケースが有効になるとは思えません。)

Data<T>から派生したクラス自体がテンプレートの特殊化である限り、上記の手法を使用してそれらをさらに制約することが機能するので、これがある程度役立つと思います。


コンパイラのサポート(これは私がテストしたものであり、他のバージョンも同様に機能する可能性があります):

  • Clang 3.3-3.6.0、-Wall -Wextra -std=c++11 -pedantic付き-上記のように機能します。
  • GCC 4.7.3-4.9.2、同じオプション-上記​​と同じ。不思議なことに、GCC 5.1.0-5.2.0は、正しいバージョンのコードを使用して部分的な特殊化を選択しなくなりました。これは回帰のように見えます。適切なバグレポートをまとめる時間はありません。よろしければお気軽にどうぞ。この問題は、テンプレートテンプレートパラメーターと一緒にパラメーターパックを使用することに関連しているようです。とにかく、GCCはenable_if_data<T>を使用して正しくないバージョンを受け入れるため、一時的な解決策になる可能性があります。
  • MSVC:/W4を使用したVisual C++ 2015は、上記のように機能します。古いバージョンでは、デフォルト引数のdecltypeが嫌いですが、手法自体は引き続き機能します。デフォルト引数を別の方法で制約を表現すると、2013 Update 4で機能します。
10
bogdan

[〜#〜] if [〜#〜]User<>の元の宣言は、

template<typename, typename=std::true_type> class User;

次に、解決策を見つけることができます(Luc Dantonのコメントに従って、 std::enable_if を使用する代わりに)

template<typename>
struct is_Data : std::false_type {};
template<typename T>
struct is_Data<Data<T>> : std::true_type {};

template<typename T>
class User<T, typename is_Data<T>::type >
{ /* ... */ };

ただし、Userの元の定義を変更する必要があるため、これは元の質問には答えませんです。私はまだより良い答えを待っていますです。これは最終的に他の解決策は不可能であることを示しますの可能性があります。

25
Walter

単一の条件がtrueの場合にのみ実装する必要があるため、最も簡単な解決策は静的アサーションを使用することです。 SFINAEは必要ありません。誤って使用すると明確なコンパイルエラーが発生し、User<>の宣言を変更する必要はありません。

template<typename T> class User {
  static_assert(is_Data<T>::value, "T is not (a subclass of) Data<>");
  /** Implementation. **/
};

参照: SFINAEの代わりにstatic_assertを使用する場合static_assertはc ++ 11構造ですが、c ++ 11以前のコンパイラには、次のような多くの回避策があります。

#define STATIC_ASSERT(consdition,name) \
  typedef char[(condition)?1:-1] STATIC_ASSERT_ ## name

user<>の宣言が変更可能で、is_Dataの値に応じて2つの実装が必要な場合は、SFINAEを使用しないソリューションもあります。

template<typename T, bool D=is_Data<T>::value> class User;

template<typename T> class User<T, true> {
  static_assert(is_Data<T>::value, "T is not (a subclass of) Data<>"); // Optional
  /* Data implementation */
};

template<typename T> class User<T, false> {
  static_assert(!is_Data<T>::value, "T is (a subclass of) Data<>"); // Optional
  /* Non-data implementation */
};

静的アサーションは、ユーザーが誤ってテンプレート引数Dを誤って指定していないかどうかを確認するだけです。 Dが明示的に指定されていない場合は、静的アサーションを省略できます。

5
bcmpinc