Javaでは、選択したクラスを拡張するタイプのみを受け入れるジェネリッククラスを定義できます。例:
public class ObservableList<T extends List> {
...
}
これは、「extends」キーワードを使用して行われます。
C ++でこのキーワードに相当する単純なものはありますか?
Javaでは、選択したクラスを拡張するタイプのみを受け入れるジェネリッククラスを定義できます。例:
public class ObservableList<T extends List> {
...
}
これは、「extends」キーワードを使用して行われます。
C ++でこのキーワードに相当する単純なものはありますか?
回答:
Boostの静的アサート機能をis_base_of
Boost Type Traitsライブラリーと組み合わせて使用することをお勧めします。
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.
[2013年6月12日の小規模な編集:宣言されているが定義されていないテンプレートを使用すると、コンパイラーではなくリンカーのエラーメッセージが表示されます。]
myBaseType
ます。Boostを閉じる前に、そのほとんどがヘッダーのみのテンプレートコードであることを知っておく必要があります。そのため、使用しないものについては、実行時にメモリや時間のコストはかかりません。また、ここで使用する特定のもの(BOOST_STATIC_ASSERT()
およびis_base_of<>
)は、宣言のみ(つまり、関数や変数の実際の定義なし)を使用して実装できるため、スペースも時間もかかりません。
static_assert(std::is_base_of<List, T>::value, "T must extend list")
。
my_template<int> x;
かmy_template<float**> y;
、コンパイラがこれらを許可my_template<char> z;
していることを確認してから、変数を宣言して許可していないことを確認できます。
ここで他の回答が指摘しているように、これは通常、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
typedefであり、コンテナのいずれかのタイプ受け入れることを望んでいるconst_iterator
とbegin
し、end
メンバ関数を返すことをconst_iterator
。これを継承するクラスに制限すると、継承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、およびタイプ特性です。
template<class T:list>
そのような問題のあるコンセプトなのかはわかりません。先端をありがとう。
まだ誰も言及していない簡単な解決策は、問題を単に無視することです。私が使用しようとするとint
ベクターやリストなどのコンテナークラスを想定している関数テンプレートでテンプレートタイプとしてと、コンパイルエラーが発生します。粗雑でシンプルですが、問題を解決します。コンパイラーは、指定されたタイプを使用しようとし、それが失敗すると、コンパイル・エラーを生成します。
これに関する唯一の問題は、表示されるエラーメッセージが読みにくくなることです。それでも、これは非常に一般的な方法です。標準ライブラリは、テンプレートタイプからの特定の動作を期待する関数またはクラステンプレートでいっぱいであり、使用されるタイプが有効であることを確認するために何もしません。
より優れたエラーメッセージが必要な場合(またはコンパイラエラーを生成しないが、意味がない場合をキャッチしたい場合)、どの程度複雑にするかによって、Boostの静的アサートまたはBoost concept_checkライブラリ。
最新のコンパイラーには、static_assert
代わりに使用できるbuilt_in があります。
T
、どこからこのコードが呼び出されますか?コンテキストがないと、そのコードスニペットを理解する機会がありません。しかし、私が言ったことは本当です。メンバー関数toString()
を持たない型を呼び出そうとするとtoString
、コンパイルエラーが発生します。
我々は使用することができますstd::is_base_of
とstd::enable_if
:
(static_assert
削除することができ、上記のクラスは、カスタム実装することができたりから使用ブースト我々は参照できない場合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
}
私の知る限り、これは現在C ++では不可能です。ただし、目的の機能を提供する新しいC ++ 0x標準に「概念」と呼ばれる機能を追加する計画があります。C ++の概念に関するこのWikipediaの記事では、より詳しく説明しています。
これで当面の問題が解決しないことはわかっていますが、新しい標準から機能を追加し始めているC ++コンパイラがいくつかあるため、概念機能をすでに実装しているコンパイラを見つけることができる場合があります。
以前の答えはすべて、木にとって森を見失ってしまったと思います。
Javaジェネリックはテンプレートと同じではありません。静的な手法であるコンパイル時のポリモーフィズムではなく、動的な手法である型の消去を使用します。これら2つの非常に異なる戦術がうまく機能しない理由は明らかです。
むしろ、実行時のいずれかをシミュレートするために、コンパイル時の構文を使用しようとするよりも、何で見てみましょう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 ++のインターフェースは、単なる抽象クラス、つまり純粋な仮想メソッドのみを実装するクラスです。この方法を使用すると、概念やテンプレートの特殊化なしで、Javaの例をC ++で簡単に実装できます。また、仮想テーブルのルックアップにより、Javaスタイルのジェネリックと同じようにパフォーマンスが低下しますが、多くの場合、これは許容できる損失です。
要旨:それをしないでください。
j_random_hackerの答えは、これを行う方法を示しています。ただし、これを行うべきではないことも指摘しておきます。テンプレートの全体の要点は、互換性のある型をすべて受け入れることができることであり、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 ++テンプレートには明示的なタイプ制約はありませんが、タイプセーフであり、正しい操作をサポートしないコードでコンパイルされません。
これはプレーンなC ++では不可能ですが、BoostのBCCLを使用するなど、コンセプトチェックを使用してコンパイル時にテンプレートパラメーターを検証できます。
C ++ 20以降、概念は言語の公式機能になりつつあります。
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構造を継承していることを確認してください。コンパイラはすべての適切な場所で混乱します。
Type::FooSecurity
はテンプレートクラスで使用されます。テンプレート引数で渡されたクラスが持っていない場合FooSecurity
、それを使用しようとするとエラーが発生します。テンプレート引数で渡されたクラスがFooSecurityでない場合、それはから派生しBase
たものではないことが確実です。
C ++ 20コンセプトの使用法
https://en.cppreference.com/w/cpp/language/constraints cppreferenceは、明示的な概念の例として継承の使用例を示しています。
template <class T, class U>
concept Derived = std::is_base_of<U, T>::value;
template<Derived<Base> T>
void f(T); // T is constrained by Derived<T, Base>
複数のベースの場合、構文は次のようになると思います:
template <class T, class U, class V>
concept Derived = std::is_base_of<U, T>::value || std::is_base_of<V, T>::value;
template<Derived<Base1, Base2> T>
void f(T);
GCC 10はそれを実装しているようです:https : //gcc.gnu.org/gcc-10/changes.htmlそして、それをUbuntu 20.04のPPAとして取得できます。https://godbolt.org/私のローカルGCC 10.1はまだ認識してconcept
いなかったので、何が起こっているのかわかりません。
C ++でこのキーワードに相当する単純なものはありますか?
番号。
達成しようとしていることに応じて、適切な(またはさらに優れた)代替が存在する場合があります。
私はいくつかのSTLコードを調べました(Linuxでは、それはSGIの実装から派生したコードだと思います)。「コンセプトアサーション」があります。たとえば、*x
およびを理解する型が必要な場合、++x
アサーションの概念には、そのコードが何もしない関数(または同様の何か)に含まれます。ある程度のオーバーヘッドが必要になるため、定義がに依存するマクロに配置する方が賢明な場合があります#ifdef debug
。
サブクラスの関係が本当に知りたい場合は、コンストラクターでそれをアサートできますT instanceof list
(ただし、C ++ではスペルが異なる)。このようにして、コンパイラーからチェックすることができないようにして、コンパイラーから出る方法をテストできます。
そのような型チェックにはキーワードはありませんが、少なくとも規則正しい方法で失敗するコードをいくつか置くことができます。
(1)関数テンプレートで特定の基本クラスXのパラメーターのみを受け入れる場合は、それを関数のX参照に割り当てます。(2)関数を受け入れるがプリミティブを受け入れない、またはその逆の場合、または他の方法でクラスをフィルター処理する場合は、受け入れたいクラスに対してのみ定義されている関数内の(空の)テンプレートヘルパー関数を呼び出します。
(1)と(2)をクラスのメンバー関数でも使用して、クラス全体でこれらの型チェックを強制できます。
あなたはおそらくあなたの痛みを和らげるためにそれをいくつかのスマートなマクロに入れることができます。:)
まあ、あなたはこのようなものを読んであなたのテンプレートを作成することができます:
template<typename T>
class ObservableList {
std::list<T> contained_data;
};
ただし、これにより制限が暗黙的になります。さらに、リストのように見えるものだけを提供することはできません。使用されるコンテナータイプを制限する方法は他にもあります。たとえば、すべてのコンテナーに存在しない特定のイテレータータイプを利用する方法がありますが、これも明示的な制限よりも暗黙的です。
私の知る限りでは、Javaステートメントを完全に反映する構文は、現在の標準には存在しません。
テンプレート内で特定のtypedefを使用して、作成したテンプレート内で使用できるタイプを制限する方法があります。これにより、特定のtypedefを含まない型のテンプレート特殊化のコンパイルが失敗することが保証されるため、特定の型を選択的にサポートするか、またはサポートしないことができます。
C ++ 11では、概念を導入することでこれが容易になるはずですが、私が望んでいるとおりに機能するとは思いません。