web-dev-qa-db-ja.com

特定のタイプのみを受け入れるC ++テンプレート

Javaでは、選択したクラスを拡張する型のみを受け入れるジェネリッククラスを定義できます。例:

public class ObservableList<T extends List> {
  ...
}

これは、「extends」キーワードを使用して行われます。

C++でこのキーワードに簡単な同等物はありますか?

137
mgamer

Boost Type Traitsライブラリの is_base_of と一緒にBoostの static assert 機能を使用することをお勧めします。

template<typename T>
class ObservableList {
    BOOST_STATIC_ASSERT((is_base_of<List, T>::value)); //Yes, the double parentheses are needed, otherwise the comma will be seen as macro argument separator
    ...
};

他のより単純なケースでは、単純にグローバルテンプレートを前方宣言できますが、有効な型に対してのみ(明示的または部分的に特殊化して)定義します。

template<typename T> class my_template;     // Declare, but don't define

// int is a valid type
template<> class my_template<int> {
    ...
};

// All pointer types are valid
template<typename T> class my_template<T*> {
    ...
};

// All other types are invalid, and will cause linker error messages.

[マイナー編集6/12/2013:宣言されているが未定義のテンプレートを使用すると、コンパイラではなく、linkerエラーメッセージが表示される。]

100
j_random_hacker

ここでの他の回答が指摘しているように、これは通常C++では保証されません。 C++では、「このクラスから継承する」以外の制約に基づいてジェネリック型を定義する傾向があります。あなたが本当にそれをやりたいなら、C++ 11と<type_traits>でとても簡単にできます:

#include <type_traits>

template<typename T>
class observable_list {
    static_assert(std::is_base_of<list, T>::value, "T must inherit from list");
    // code here..
};

しかし、これは人々がC++で期待する多くの概念を壊します。独自の特性を定義するなどのトリックを使用することをお勧めします。たとえば、observable_listは、タイプ定義const_iteratorと、const_iteratorを返すbeginおよびendメンバー関数を持つ任意のタイプのコンテナを受け入れたいかもしれません。これをlistから継承するクラスに制限すると、listから継承せず、これらのメンバー関数とtypedefを提供する独自の型を持つユーザーは、observable_listを使用できなくなります。

この問題には2つの解決策がありますが、その1つは何も制約せず、アヒルのタイピングに依存することです。このソリューションの大きな欠点は、大量のエラーが関係することであり、ユーザーが理解しにくい場合があります。別の解決策は、インターフェイス要件を満たすために提供されるタイプを制約するために特性を定義することです。このソリューションの大きな欠点は、余分な記述が煩わしいと思われることです。ただし、良い面は、static_assertのように独自のエラーメッセージを作成できることです。

完全を期すために、上記の例の解決策を示します。

#include <type_traits>

template<typename...>
struct void_ {
    using type = void;
};

template<typename... Args>
using Void = typename void_<Args...>::type;

template<typename T, typename = void>
struct has_const_iterator : std::false_type {};

template<typename T>
struct has_const_iterator<T, Void<typename T::const_iterator>> : std::true_type {};

struct has_begin_end_impl {
    template<typename T, typename Begin = decltype(std::declval<const T&>().begin()),
                         typename End   = decltype(std::declval<const T&>().end())>
    static std::true_type test(int);
    template<typename...>
    static std::false_type test(...);
};

template<typename T>
struct has_begin_end : decltype(has_begin_end_impl::test<T>(0)) {};

template<typename T>
class observable_list {
    static_assert(has_const_iterator<T>::value, "Must have a const_iterator typedef");
    static_assert(has_begin_end<T>::value, "Must have begin and end member functions");
    // code here...
};

上記の例には、C++ 11の機能を紹介する多くの概念があります。好奇心をそそる検索用語には、可変長テンプレート、SFINAE、式SFINAE、および型特性があります。

103
Rapptz

誰もまだ言及していない単純な解決策は、単に問題を無視することです。 intを、ベクトルやリストなどのコンテナクラスを想定している関数テンプレートのテンプレートタイプとして使用しようとすると、コンパイルエラーが発生します。粗野でシンプルですが、問題は解決します。コンパイラは、指定されたタイプを使用しようとしますが、それが失敗すると、コンパイルエラーが生成されます。

それに関する唯一の問題は、あなたが得るエラーメッセージが読みにくいということです。それにもかかわらず、これは非常に一般的な方法です。標準ライブラリには、テンプレートタイプから特定の動作を期待する関数またはクラステンプレートが多数あり、使用されているタイプが有効であることを確認するために何も行いません。

より良いエラーメッセージが必要な場合(または、コンパイラエラーを生成しないが、それでも意味をなさないケースをキャッチしたい場合)、どの程度複雑にするかによって、Boostの静的アサートまたはBoost concept_checkライブラリ。

最新のコンパイラでは、代わりに使用できるbuilt_in static_assertがあります。

56
jalf

私の知る限り、これは現在C++では不可能です。ただし、新しいC++ 0x標準には、探している機能を提供する「概念」と呼ばれる機能を追加する計画があります。この ウィキペディアの記事 C++の概念については、より詳細に説明します。

すぐに問題が解決するわけではありませんが、新しい標準の機能を追加し始めたC++コンパイラがいくつかあるため、概念機能を既に実装しているコンパイラを見つけることができるかもしれません。

13
Barry Carr

std::is_base_ofstd::enable_if を使用できます:
static_assert は削除できます。上記のクラスはカスタム実装するか、 boostから使用できます 参照できない場合 type_traits

#include <type_traits>
#include <list>

class Base {};
class Derived: public Base {};

#if 0   // wrapper
template <class T> class MyClass /* where T:Base */ {
private:
    static_assert(std::is_base_of<Base, T>::value, "T is not derived from Base");
    typename std::enable_if<std::is_base_of<Base, T>::value, T>::type inner;
};
#Elif 0 // base class
template <class T> class MyClass: /* where T:Base */
    protected std::enable_if<std::is_base_of<Base, T>::value, T>::type {
private:
    static_assert(std::is_base_of<Base, T>::value, "T is not derived from Base");
};
#Elif 1 // list-of
template <class T> class MyClass /* where T:list<Base> */ {
    static_assert(std::is_base_of<Base, typename T::value_type>::value , "T::value_type is not derived from Base");
    typedef typename std::enable_if<std::is_base_of<Base, typename T::value_type>::value, T>::type base; 
    typedef typename std::enable_if<std::is_base_of<Base, typename T::value_type>::value, T>::type::value_type value_type;

};
#endif

int main() {
#if 0   // wrapper or base-class
    MyClass<Derived> derived;
    MyClass<Base> base;
//  error:
    MyClass<int> wrong;
#Elif 1 // list-of
    MyClass<std::list<Derived>> derived;
    MyClass<std::list<Base>> base;
//  error:
    MyClass<std::list<int>> wrong;
#endif
//  all of the static_asserts if not commented out
//  or "error: no type named ‘type’ in ‘struct std::enable_if<false, ...>’ pointing to:
//  1. inner
//  2. MyClass
//  3. base + value_type
}
11
firda

型リストから派生した型Tのみを受け入れる同等物は次のようになります

template<typename T, 
         typename std::enable_if<std::is_base_of<List, T>::value>::type* = nullptr>
class ObservableList
{
    // ...
};
10
nh_

これまでのすべての答えは、樹木が森を見失っていると思います。

Javaジェネリックは、テンプレートとは異なります。 type erasureを使用します。これは、ではなくdynamic techniqueです。コンパイル時ポリモーフィズム、これは静的手法です。これら2つの非常に異なる戦術がうまくゲル化しない理由は明らかです。

コンパイル時の構成を使用してランタイムをシミュレートするのではなく、extendsが実際に行うことを見てみましょう。 Stack Overflowによる および Wikipedia 、extendsサブクラス化を示すために使用されます。

C++はサブクラス化もサポートしています。

また、ジェネリックの形式で型消去を使用し、型チェックを実行するように拡張されたコンテナクラスも示します。 C++では、自分で型消去機構を実行する必要があります。これは簡単です。スーパークラスへのポインターを作成します。

クラス全体を作成するのではなく、typedefにラップして使いやすくします。

typedef std::list<superclass*> subclasses_of_superclass_only_list;

例えば:

class Shape { };
class Triangle : public Shape { };

typedef std::list<Shape*> only_shapes_list;
only_shapes_list shapes;

shapes.Push_back(new Triangle()); // Works, triangle is kind of shape
shapes.Push_back(new int(30)); // Error, int's are not shapes

今、Listは一種のコレクションを表すインターフェースであるようです。 C++のインターフェイスは、単に抽象クラス、つまり純粋な仮想メソッドのみを実装するクラスになります。このメソッドを使用すると、コンセプトやテンプレートの特殊化なしで、C++でJavaの例を簡単に実装できます。また、仮想テーブルのルックアップのため、Javaスタイルのジェネリックと同じくらい遅いパフォーマンスになりますが、これは許容できる損失になることがよくあります。

7
Alice

エグゼクティブサマリー:それをしないでください。

j_random_hackerの答えは、これを行うためにhowを教えてくれます。ただし、notを実行する必要があることも指摘したいと思います。テンプレートの要点は、互換性のあるすべての型を受け入れることができるということであり、Javaスタイルの型制約はそれを破ります。

Javaの型制約はバグであり、機能ではありません。 Javaはジェネリックの型消去を行うため、Javaは型パラメーターの値だけに基づいてメソッドを呼び出す方法を理解できないため、それらが存在します。

一方、C++にはそのような制限はありません。テンプレートパラメータタイプは、使用される操作と互換性のある任意のタイプにすることができます。共通の基本クラスは必要ありません。これはPythonの「Duck Typing」に似ていますが、コンパイル時に行われます。

テンプレートの力を示す簡単な例:

// Sum a vector of some type.
// Example:
// int total = sum({1,2,3,4,5});
template <typename T>
T sum(const vector<T>& vec) {
    T total = T();
    for (const T& x : vec) {
        total += x;
    }
    return total;
}

この和関数は、正しい演算をサポートする任意のタイプのベクトルを合計できます。 int/long/float/doubleなどのプリミティブと、+ =演算子をオーバーロードするユーザー定義の数値型の両方で機能します。ちなみに、文字列は+ =をサポートしているため、この関数を使用して文字列を結合することもできます。

プリミティブのボックス化/ボックス化解除は不要です。

T()を使用してTの新しいインスタンスも構築することに注意してください。これは、暗黙的なインターフェイスを使用するC++では簡単ですが、型制約のあるJavaでは実際には不可能です。

C++テンプレートには明示的な型制約はありませんが、それでも型安全であり、正しい操作をサポートしないコードではコンパイルされません。

6
catphive

これはプレーンなC++では不可能ですが、コンパイル時にコンセプトチェックを通じてテンプレートパラメーターを検証できます。 Boost's BCCL を使用します。

5
macbirdie
class Base
{
    struct FooSecurity{};
};

template<class Type>
class Foo
{
    typename Type::FooSecurity If_You_Are_Reading_This_You_Tried_To_Create_An_Instance_Of_Foo_For_An_Invalid_Type;
};

派生クラスがFooSecurity構造を継承していることを確認してください。そうすれば、コンパイラはすべての適切な場所で動揺します。

5
Stuart

C++でこのキーワードに簡単な同等物はありますか?

番号。

あなたが達成しようとしているものに応じて、適切な(またはさらに良い)代替物があるかもしれません。

私はいくつかのSTLコードを調べてきました(Linuxでは、SGIの実装に由来するものだと思います)。 「コンセプトアサーション」があります。たとえば、*xおよび++xを理解する型が必要な場合、概念アサーションには、そのコードが何もしない関数(または同様のもの)に含まれます。多少のオーバーヘッドが必要なので、定義が#ifdef debugに依存するマクロに入れるのが賢明かもしれません。

サブクラスの関係が本当に知りたいものである場合、コンストラクターでT instanceof listをアサートできます(ただし、C++では異なる「スペル」が使用されます)。そうすることで、コンパイラーから抜け出す方法をテストできます。

1
Jonas Kölker

そのような型チェックにはキーワードはありませんが、少なくとも規則正しい方法で失敗するコードを入れることができます:

(1)関数テンプレートが特定の基本クラスXのパラメーターのみを受け入れるようにする場合は、関数のX参照に割り当てます。 (2)プリミティブではなく関数を受け入れたい場合、または他の方法でクラスをフィルター処理したい場合、受け入れたいクラスに対してのみ定義されている(空の)テンプレートヘルパー関数を関数内で呼び出します。

クラスのメンバー関数でも(1)と(2)を使用して、クラス全体でこれらの型チェックを強制できます。

おそらく、それをスマートマクロに入れて痛みを和らげることができます。 :)

1
Jaap