私はこのクラスの構造を持っています。
class Interface{
...
}
class Foo : public Interface{
...
}
template <class T>
class Container{
...
}
そして、私は他のクラスBarのこのコンストラクターを持っています。
Bar(const Container<Interface> & bar){
...
}
この方法でコンストラクターを呼び出すと、「一致する関数がありません」というエラーが発生します。
Container<Foo> container ();
Bar * temp = new Bar(container);
なにが問題ですか?テンプレートは多形ではありませんか?
必要なものの正確な用語は「テンプレート共分散」だと思います。つまり、BがAから継承する場合、どういうわけかT<B>
はT<A>
から継承します。これはC++には当てはまらず、JavaおよびC#ジェネリック*にも当てはまりません。
テンプレートの共分散を回避するのには十分な理由があります。これにより、テンプレートクラスのすべての型安全性が削除されます。次の例で説明しましょう。
//Assume the following class hierarchy
class Fruit {...};
class Apple : public Fruit {...};
class Orange : public Fruit {...};
//Now I will use these types to instantiate a class template, namely std::vector
int main()
{
std::vector<Apple> Apple_vec;
Apple_vec.Push_back(Apple()); //no problem here
//If templates were covariant, the following would be legal
std::vector<Fruit> & fruit_vec = Apple_vec;
//Push_back would expect a Fruit, so I could pass it an Orange
fruit_vec.Push_back(Orange());
//Oh no! I just added an orange in my Apple basket!
}
したがって、AとBの関係に関係なく、T<A>
とT<B>
は完全に無関係なタイプと見なす必要があります。
では、直面している問題をどのように解決できますか? JavaおよびC#では、それぞれ制限付きワイルドカードおよびconstraints:
//Java code
Bar(Container<? extends Interface) {...}
//C# code
Bar<T>(Container<T> container) where T : Interface {...}
次のC++標準(C++ 1x(以前のC++ 0x)として知られている)には、最初は Concepts という名前のさらに強力なメカニズムが含まれていました。これにより、開発者はテンプレートに構文要件や意味要件を適用できます。パラメータが、残念ながら後日延期されました。ただし、Boostには コンセプトチェックライブラリ があります。
それでも、概念は遭遇する問題に対して少しやり過ぎかもしれません。 @ gf によって提案された単純な静的アサーションを使用することがおそらく最良の解決策です。
*更新:.Net Framework 4以降、ジェネリックパラメーターが 共変または反変 であるとマークすることが可能です。
ここには2つの問題があります。デフォルトの構造はMyClass c;
の形式です。括弧を付けると、コンパイラーにとっては関数宣言のように見えます。
もう1つの問題は、Container<Interface>
が単にContainer<Foo>
とは異なるタイプであるということです。代わりに、実際にポリモーフィズムを取得するために次のことを行うことができます。
Bar::Bar(const Container<Interface*>&) {}
Container<Interface*> container;
container.Push_back(new Foo);
Bar* temp = new Bar(container);
またはもちろん、Kornelが示したように、Bar
またはそのコンストラクターをテンプレートにすることもできます。
型に安全なコンパイル時のポリモーフィズムが実際に必要な場合は、Boost.TypeTraitsis_base_of または同等のものを使用できます。
template<class T>
Bar::Bar(const Container<T>& c) {
BOOST_STATIC_ASSERT((boost::is_base_of<Interface, T>::value));
// ... will give a compile time error if T doesn't
// inherit from Interface
}
いいえ。コンテナパラメータが、それが定義するクラスに「ハードコード」されていると想像してください(実際にはそのように機能します)。したがって、コンテナタイプはContainer_Foo
であり、Container_Interface
とは互換性がありません。
しかし、あなたが試みるかもしれないことはこれです:
template<class T>
Bar(const Container<T> & bar){
...
}
しかし、あなたはそのように直接型チェックを失います。
実際には、STLの方法(おそらくより効果的で一般的)は
template<class InputIterator>
Bar(InputIterator begin, InputIterator end){
...
}
...しかし、コンテナにイテレータが実装されていないと思います。
テンプレート関数を使用する次の回避策を提案します。この例ではQtのQListを使用していますが、ソリューションが他のコンテナーに直接転置されることを妨げるものは何もありません。
template <class D, class B> // D (Derived) inherits from B (Base)
QList<B> toBaseList(QList<D> derivedList)
{
QList<B> baseList;
for (int i = 0; i < derivedList.size(); ++i) {
baseList.append(derivedList[i]);
}
return baseList;
}
長所:
短所:
データの継承ツリーを反映して、コンテナの継承ツリーを作成することができます。次のデータがある場合:
class Interface {
public:
virtual ~Interface()
{}
virtual void print() = 0;
};
class Number : public Interface {
public:
Number(int value) : x( value )
{}
int get() const
{ return x; }
void print()
{ std::printf( "%d\n", get() ); };
private:
int x;
};
class String : public Interface {
public:
String(const std::string & value) : x( value )
{}
const std::string &get() const
{ return x; }
void print()
{ std::printf( "%s\n", get().c_str() ); }
private:
std::string x;
};
次のコンテナを使用することもできます。
class GenericContainer {
public:
GenericContainer()
{}
~GenericContainer()
{ v.clear(); }
virtual void add(Interface &obj)
{ v.Push_back( &obj ); }
Interface &get(unsigned int i)
{ return *v[ i ]; }
unsigned int size() const
{ return v.size(); }
private:
std::vector<Interface *> v;
};
class NumericContainer : public GenericContainer {
public:
virtual void add(Number &obj)
{ GenericContainer::add( obj ); }
Number &get(unsigned int i)
{ return (Number &) GenericContainer::get( i ); }
};
class TextContainer : public GenericContainer {
public:
virtual void add(String &obj)
{ GenericContainer::add( obj ); }
String &get(unsigned int i)
{ return (String &) GenericContainer::get( i ); }
};
これは最高のパフォーマンスのコードではありません。それはただアイデアを与えることです。このアプローチの唯一の問題は、新しいデータクラスを追加するたびに、新しいコンテナも作成する必要があることです。それとは別に、あなたは「再び働く」多型を持っています。具体的または一般的である可能性があります。
void print(GenericContainer & x)
{
for(unsigned int i = 0; i < x.size(); ++i) {
x.get( i ).print();
}
}
void printNumbers(NumericContainer & x)
{
for(unsigned int i = 0; i < x.size(); ++i) {
printf( "Number: " );
x.get( i ).print();
}
}
int main()
{
TextContainer strContainer;
NumericContainer numContainer;
Number n( 345 );
String s( "Hello" );
numContainer.add( n );
strContainer.add( s );
print( strContainer );
print( numContainer );
printNumbers( numContainer );
}
#include <iostream>
#include <sstream>
#include <map>
#include <vector>
struct Base { int b = 111; };
struct Derived: public Base { };
struct ObjectStringizer {
template <typename T>
static std::string to_string(const T& t) {
return helper<T>()(t);
}
template <typename T, typename = void>
struct helper {
std::string operator()(const T& t) {
std::ostringstream oss;
oss << t;
return oss.str();
}
};
template <typename T>
struct helper<T, typename std::enable_if<std::is_base_of<Base, T>::value>::type> {
std::string operator()(const T& base) {
return to_string(base.b);
}
};
template <typename T>
struct helper<std::vector<T>> {
std::string operator()(const std::vector<T>& v) {
std::ostringstream oss;
for (size_t i = 0, sz = v.size(); i < sz; ++i) {
oss << (i ? "," : "") << to_string(v[i]);
}
return "[" + oss.str() + "]";
}
};
template <typename Key, typename Value>
struct helper<std::map<Key, Value>> {
std::string operator()(const std::map<Key, Value>& m) {
std::ostringstream oss;
for (auto iter = m.begin(), iter_end = m.end(); iter_end != iter; ++iter) {
oss << (m.begin() != iter ? "," : "") << to_string(iter->first) << ":" << to_string(iter->second);
}
return "{" + oss.str() + "}";
}
};
};
int main(int argc, char* argv[]) {
std::cout << ObjectStringizer::to_string("hello ") << ObjectStringizer::to_string(std::string("world")) << std::endl;
std::cout << ObjectStringizer::to_string(Derived()) << std::endl;
std::cout << ObjectStringizer::to_string(std::vector<int>{3, 5, 7, 9}) << std::endl;
std::cout << ObjectStringizer::to_string(std::map<int, std::string>{{1, "one"}, {2, "two"}}) << std::endl;
return 0;
}