Template specialization - any way to inherit from template class / add features?

Started by
6 comments, last by Koobazaur 16 years, 3 months ago
I have a custom container template class that stores some data as

private:
T _Data;
to retrieve one, one would call the Data() function

T & Data()
{
return _Data;
}
Now I want to create a specialized case where my data is a std::map<key, data>. However, to retrieve the contents I would now have to call: MyContainer.Data()[key] = 12; Which is very clumsy. I'd like to be able to call [] directly on the container, as such MyContainer[key] = 12; Naturally, I can do this through template specialization... but then, I must copy the whole template class, with ALL it's functions. This isn't very convinient because it adds extra unnecessary code and, if in the future I decided to change some implementation of my container, I'd need to go in and change the specialization as well. Furthermore, it isn't even necessary as I am not changing any of the template functions, but merely adding a new function (operator[]). Thus, is there any way to add this extra functionality WITHOUT the overhead of having to copy and re-define all of my base template class parameters? I cannot define a global operator[] (like I could with operator=) so that's out of the question, and I do not want to create a base and derived classes as, the way my container works, it would also result in copy/pasting lots of code, with an added dynamic memory management on top of it for polymorphism... (Also, as a side note, about templates - I know I can put full template function declarations in my headers as they will not be compiled like regular functions, thus I won't get any "function foo already defined." I am assuming I can do the same with template classes even if not all of my class's functions are template functions - thus I can define the whole template class (even its non template methods) in the header, correct?)
Comrade, Listen! The Glorious Commonwealth's first Airship has been compromised! Who is the saboteur? Who can be saved? Uncover what the passengers are hiding and write the grisly conclusion of its final hours in an open-ended, player-driven adventure. Dziekujemy! -- Karaski: What Goes Up...
Advertisement
Quote:I am assuming I can do the same with template classes even if not all of my class's functions are template functions


Functions in a class are Methods. They are integral part of class. Hence, a method of a templated class is unique to class' template instance.

template < typename T >struct Foo {  void bar(); <-- templated function, specialized for T};
The reason for this is simple:
template < typename T >struct Foo {  T value;  void bar() {    std::cout << value; <-- depends on template parameter,                             hence a new instance of method must be made  }};


Quote:Naturally, I can do this through template specialization... but then, I must copy the whole template class, with ALL it's functions. This isn't very convinient because it adds extra unnecessary code and, if in the future I decided to change some implementation of my container, I'd need to go in and change the specialization as well.


Create a templated base class?

struct NonTemplatedBase // No templates{  void foo();  void bar();};template < class T >              // parameter dependant functionalitystruct StorageBase : public NonTemplatedBase {  // everything else  T & Data()protected:  // lots of everything else  T _Data;  // yet more stuff};template < class T >struct Storage : public<T>{  // nothing, except for constructors};template < class K, class V > Storage< std::map<K,V> > : public< std::map<K,V> >{  // nothing, except for constructors  V & operator[]( K &k )  {    return std::map[k];  }};


Although I think you're trying to put two incompatible concepts into same class.

Quote:and I do not want to create a base and derived classes as, the way my container works, it would also result in copy/pasting lots of code, with an added dynamic memory management on top of it for polymorphism...


In that case your class is doing way too much stuff, and it's time to break it apart and split the functionality. You want one container to rule them all. It won't end well.
I suppose, if you really wanted to do this, you could use a traits class that you specialize for types that you want to be usable with []. Ex:
template <typename T>struct WrapperTraits {  typedef int mapped_type;  typedef int key_type;  static const bool allow_index = false;};template <typename KeyType, typename MappedType> struct WrapperTraits<std::map<KeyType, MappedType> > {  typedef KeyType key_type;  typedef MappedType mapped_type;  static const bool allow_index = true;};template <typename T>class Wrapper {  public:          T & get_data(void)       { return data_; }    const T & get_data(void) const { return data_; }        typename WrapperTraits<T>::mapped_type & operator[](const typename WrapperTraits<T>::key_type & key) {      BOOST_STATIC_ASSERT(WrapperTraits<T>::allow_index);      return data_[key];    }  private:    T data_;};


And yes, the entire definition of a template class can go into a header. (And quite often needs to.)
It sounds as if (unless I missed some detail) that you could derive a new template class, MyMapContainer< K , T > publicly from MyContainer< std::map< K , T > > then add the index operator ([]) to the new class. This has another benefit in that any utility methods that don't particularly care about individual items inside the map container will work just fine with either class.

Another possibility that might work, IIRC (and is still a bit of a hack anyhow,) would be to declare the index operator as a template method taking a Key type and providing no default definition, only a specialization for std::map. If I'm not mistaken, the template method will only be generated by the compiler if you attempt to use it on that type's container, so it won't be generated for things like MyContainer<int>, so long as you don't attempt to use the index operator on it. Also, this method would allow the possibility that the index operator template K might not match the std::map template K, creating a compile error. The top method handily avoids this problem, as both Ks are defined by a single template parameter.

In any case, the first method is the more orthodox and safer approach, the latter is more of a "That's just crazy enough to work" sort of approach.

throw table_exception("(? ???)? ? ???");

Quote:Original post by ravyne2001
Another possibility that might work, IIRC (and is still a bit of a hack anyhow,) would be to declare the index operator as a template method taking a Key type and providing no default definition, only a specialization for std::map. If I'm not mistaken, the template method will only be generated by the compiler if you attempt to use it on that type's container, so it won't be generated for things like MyContainer<int>, so long as you don't attempt to use the index operator on it. Also, this method would allow the possibility that the index operator template K might not match the std::map template K, creating a compile error. The top method handily avoids this problem, as both Ks are defined by a single template parameter.


Function bodies aren't generated unless referenced or explicitly instantiated; however, function signatures still need to be valid. In other words, this technique won't work since you can't properly generate a function signature without a level of indirection such as the traits system in my post.
Quote:Original post by SiCrane
Quote:Original post by ravyne2001
Another possibility that might work, IIRC (and is still a bit of a hack anyhow,) would be to declare the index operator as a template method taking a Key type and providing no default definition, only a specialization for std::map. If I'm not mistaken, the template method will only be generated by the compiler if you attempt to use it on that type's container, so it won't be generated for things like MyContainer<int>, so long as you don't attempt to use the index operator on it. Also, this method would allow the possibility that the index operator template K might not match the std::map template K, creating a compile error. The top method handily avoids this problem, as both Ks are defined by a single template parameter.


Function bodies aren't generated unless referenced or explicitly instantiated; however, function signatures still need to be valid. In other words, this technique won't work since you can't properly generate a function signature without a level of indirection such as the traits system in my post.


I'll have to check my sources when I get home, but I believe I've used something similar to only allow an "allowable" subset of template parameters to be constructible in a value class I created. I may simply be mistaken, describing it incorrectly or poorly, recalling some mish-mash of code from multiple approaches, or overstepping what can be accomplished.

In any case, its really a hack at best (presuming that I do actually recall it, or similar, working correctly) so the other solution is strongly preferable.

throw table_exception("(? ???)? ? ???");

Again, the problem is the function signature. The types for both the arguments and the return values still need to be parsed. And since the return type is dependent on the template type parameter, and not the argument type, you need an extra level of indirection in order to get that to parse.
Thanks for the suggestions. Let me explain why I don't want to go with inhertiance.

Basically, my class is a Tree which contains an std::map of it's children of the same type (plus my template data as I specified). Hence, if I use inhertience, the inherited class would still have a map of the base class, not itself. Now I could use polymorphism and store pointers to base class and define all its functions as virtual... but then, each of my derived class will have to re-define half of them, and I end up copy/pasting half the code as with template specialization, plus having to deal with dynamic memor management for polymorphism.

EDIT: well, keeping my base class a template but changing my map of children to pointers allowed me to create a derived class that does what I want by only re-defining 3 functions (Clone(), AccessChild(key) and const AccessChild(key)), which are pretty much the same as parent but differ only in the type of returned variable (they return reference the derived class instead of parent class). Not sure if I like this solution (especially since I stati_cast the base pointer from base class to derived class when overloading the AccessChild function), but at least it does what I wanted with minimal code repeat...

[Edited by - Koobazaur on January 5, 2008 12:08:43 AM]
Comrade, Listen! The Glorious Commonwealth's first Airship has been compromised! Who is the saboteur? Who can be saved? Uncover what the passengers are hiding and write the grisly conclusion of its final hours in an open-ended, player-driven adventure. Dziekujemy! -- Karaski: What Goes Up...

This topic is closed to new replies.

Advertisement