开发者

Templates polymorphism

I have this structure of classes.

class Interface {
  // ...
};

class Foo : public Interface {
  // ...
};

template <class T>
class Container {
  // ...
};

And I have this constructor of some other class Bar.

Bar(const Container<Interface> & bar){
  // ...
}

When I call the constructor this way I get a "no matching function" error.

Container<Foo> container 开发者_Go百科();

Bar * temp = new Bar(container);

What is wrong? Are templates not polymorphic?


I think the exact terminology for what you need is "template covariance", meaning that if B inherits from A, then somehow T<B> inherits from T<A>. This is not the case in C++, nor it is with Java and C# generics*.

There is a good reason to avoid template covariance: this will simply remove all type safety in the template class. Let me explain with the following example:

//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!
}

Consequently, you should consider T<A> and T<B> as completely unrelated types, regardless of the relation between A and B.

So how could you solve the issue you're facing? In Java and C#, you could use respectively bounded wildcards and constraints:

//Java code
Bar(Container<? extends Interface) {...}

//C# code
Bar<T>(Container<T> container) where T : Interface {...}

The next C++ Standard (known as C++1x (formerly C++0x)) initially contained an even more powerful mechanism named Concepts, that would have let developers enforce syntaxic and/or semantic requirements on template parameters, but was unfortunately postponed to a later date. However, Boost has a Concept Check library that may interest you.

Nevertheless, concepts might be a little overkill for the problem you encounter, an using a simple static assert as proposed by @gf is probably the best solution.

* Update: Since .Net Framework 4, it is possible to mark generic parameters has being covariant or contravariant.


There are two problems here: default constructions have the form MyClass c;; with parentheses it looks like a function declaration to the compiler.

The other problem is that Container<Interface> is simply a different type then Container<Foo> - you could do the following instead to actually get polymorphism:

Bar::Bar(const Container<Interface*>&) {}

Container<Interface*> container;
container.push_back(new Foo);
Bar* temp = new Bar(container);

Or of course you could make Bar or its constructor a template as Kornel has shown.

If you actually want some type-safe compile-time polymorphism, you could use Boost.TypeTraits is_base_of or some equivalent:

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
}


No. Imagine that the container parameter is "hardcoded" into the class it defines (and that is actually how it works). Hence the container type is Container_Foo, that is not compatible with Container_Interface.

What you might try however is this:

template<class T>
Bar(const Container<T> & bar){
...
}

Yet you loose direct type checking that way.

Actually the STL way (probably more effective and generic) would be to do

template<class InputIterator>
Bar(InputIterator begin, InputIterator end){
...
}

... but I assume you don't have iterators implemented in the container.


It is possible to create an inheritance tree for containers, reflecting the inheritance tree of the data. If you have the following data:

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;
};

You could also have the following containers:

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 ); }
};

This is not the best performing code; it is just to give an idea. The only problem with this approach is that every time you add a new Data class, you have to also create a new Container. Apart from that, you have polymorphism "working again". You can be specific or general:

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 );
}


I propose the following workaround, which employs a template function. Although the example use Qt's QList, nothing prevents the solution from being straightforwardly transposed to any other container.

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;
}

Pros:

  • general
  • type-safe
  • fairly efficient if the items are pointers or some other cheaply copy-constructible elements (such as implicitly shared Qt classes)

Cons:

  • requires the creation of a new container, as opposed to enabling the reuse of the original one
  • implies some memory and processor overhead both to create and to populate the new container, which depend heavily on the cost of the copy-constructor


#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;
}


container is a container of Foo objects not a container of Interface objects

And it cannot be polymorphic either, pointers to things can be ,but not the objects themselvs. How big would the slots in the container have to be for container if you could put anything derived from interface in it

you need

 container<Interface*>

or better

 container<shared_ptr<Interface> >
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜