Jump to content
  • Advertisement
Sign in to follow this  
Falken42

[C++] virtual member functions in a templated class

This topic is 3497 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I'm currently working with this templated class:
template <typename T>
class Hoge
{
    void foo();
    void bar(const Hoge &hoge)
    {
        if (member == hoge)    // <- note operator== used here
            ...do stuff...
    }
};

I plan to extend this class so it can be derived from, and the base class can be used to call the virtual methods in the derived. So I now have this:
template <typename T>
class Hoge
{
    virtual void foo();
    virtual void bar(const Hoge<T> &hoge)
    {
        if (member == hoge)
            ...do stuff...
    }
};

The only thing that has been changed is "virtual" has been added before each of the function names. If I declare this class with a non-POD type of T, the compiler complains that my class type of T does not have an operator==, even though I never call the bar() function from my code for that type. Both MSVC2005 and gcc-4.1.2 give me the same error. If I remove the virtual keyword from bar(), but leave it for foo(), the error goes away. I was under the impression that functions in a templated class weren't instanciated until they were actually used. Can anyone enlighten me as to why this isn't the case with virtuals? And is there any way around this so that I don't have to require the users of my template class to add an operator== to their code if they'll never need it?

Share this post


Link to post
Share on other sites
Advertisement
This is an artifact of the way virtual functions work -- they get entered into a table of function pointers, at which point it's very hard (and generally not useful) for the compiler to be able to figure out which ones are called and which ones aren't.

There might be a workaround for your situation -- care to post some actual code instead of this foo and bar nonsense?

Share this post


Link to post
Share on other sites
I don't know this for certain, but if I had to speculate I would say that, essentially, any function that goes into the v-table is treated as instantiated.

When you think about it, the entries in the v-table must always "line up" for each derived class -- you can't just drop things from the v-table because a derived class might use them. If this space must be reserved in the v-table, then it has to have something to point at, to have something to point at, that code has to be compiled and located somewhere in memory -- and if that happens, all the usual requirements apply.

Does the intended non-POD type purposely not provide the equality operator, or is it an issue of not wanting to write it? If equality does not make sense for the type, would introducing a dummy equality operator be acceptable?

Share this post


Link to post
Share on other sites
Quote:
Original post by MaulingMonkey
There might be a workaround for your situation -- care to post some actual code instead of this foo and bar nonsense?

The actual code is quite large and not something that I can just post. The same results can be achieved using the snippets I've provided, however.

Part of the problem is this class provides quite a bit of generic functionality, and various types of T use the functions in the class that they need. You might suggest that I could split up the class based on the different classes of T, but that's not really an ideal solution here.

Quote:
Original post by Ravyne
Does the intended non-POD type purposely not provide the equality operator, or is it an issue of not wanting to write it? If equality does not make sense for the type, would introducing a dummy equality operator be acceptable?

I would say not wanting to write the equality operator is part of it, but in some cases where the template is used, equality doesn't make sense -- and in those cases, the class doesn't ever call the bar() function in the template.

Is it possible to create a dummy equality operator somehow, without too much extra code? :)

Thanks to both of you for your replies -- at least I know now that even templated classes are instanciated due to the v-table.

Share this post


Link to post
Share on other sites
Sidenote: Inside the class declaration Class<T>, you don't have to use Class<T> but only Class:

template <typename T>
class Hoge
{
virtual void foo();
virtual void bar(const Hoge &hoge) // Hoge<T> only necessary
// class declaration
{
if (member == hoge)
...do stuff...
}
};


But that shouldn't cause errors.

Sidenote2: Virtual Member Function Templates (not in your case, just saying as it might be interesting here; you only have a Virtual Member Function) cannot be declared (8.1.1 Virtual Member Functions, in the Book).

Further, as you have only posted pseudo code, I can only guess what fits your needs.

Solution:
You have to actually define a fitting operator==:

template <typename T>
class Hoge {
T member;
virtual void foo() {};
virtual void bar(const Hoge &hoge) {
if (member == hoge)
;
}
};

template <typename T>
inline bool operator == (T const &lhs, Hoge<T> const &rhs) {
return true;
}

int main () {
Hoge<int> hoge;
}


Solution B:
As Ravyne pointed out, at the point of instantiation, vtables must be derived by the compiler. I fail to find a reference, but my personal guess is that the body of the function is needed and hence compiled because e.g. another unit of translation can potentially define a class that derives from an instantiation of your class template, but that deriving class doesn't override your abstract function. Then, that unit of translation would need the definition. The translation unit of your template couldn't know.

Otoh this reason might be easier to stomach: If your function is not purely virtual (as in "virtual T f() = 0;"), the vtable entry can't be initialised with 0, but must be initialized with the address of an existing function. A class template member function can obviously only exist when it is instantiated. While not impossible, it would cause a lot of epic PITA to manage a partially initialized, partially uninitialized vtable. It could cause PITA for the compiler, but could also end in performance trouble at runtime.

Share this post


Link to post
Share on other sites
Crap. In my haste to post before a meeting, I realize I made a fatal mistake with the initial code sample I provided. My apologies to everyone, I really should have waited and double checked the content afterward.

Here's the relevant functions in the class and some sample code. The Hoge class provides a container for objects of any type, wrapped around std::vector. The class really does much more than this, but as I said earlier, I can't post it all.

template <typename T>
class Hoge
{
std::vector<T> mList;

public:
virtual void add(const T &var)
{
mList.push_back(var);
}

virtual bool remove(const T &var) // <- parameter is of type 'T', not 'Hoge'
{
std::vector<T>::iterator iter = mList.begin();

for (; iter < mList.end(); ++iter)
{
if (var == *iter)
{
mList.erase(iter);
return true;
}
}

return false;
}
};



With a POD type, this works just fine, but it fails when using a non-POD type that doesn't implement an eqality operator:

class SomeClass
{
public:
SomeClass() : mData(NULL)
{
}

~SomeClass() { };

private:
SomePtr *mData;
};

void main(void)
{
Hoge<int> foo;

// works fine
foo.add(5);
foo.add(2);

// error C2678: binary '==' : no operator found which takes a left-hand operand of type 'const SomeClass' (or there is no acceptable conversion)
Hoge<SomeClass> bar;
SomeClass a, b;

bar.add(a);
bar.add(b);
}



So does this mean I have to implement operator== in SomeClass, regardless? Sticking something in my source code like...
template <typename T>
inline bool operator==(const T &a, const T &b)
{
return true;
}


while compiles, is really just asking for trouble. :)

Share this post


Link to post
Share on other sites
Quote:

So does this mean I have to implement operator== in SomeClass, regardless?

Yes, if you want to be able to use it as a template parameter to your "Hoge" class.

I am curious though, what kind of functionality do you plan to extend "Hoge" with?

Share this post


Link to post
Share on other sites
Quote:
Original post by rip-off
I am curious though, what kind of functionality do you plan to extend "Hoge" with?


Presently two extensions are planned. One will provide an insertion sort, placing the add()ed value into a proper position into the list.

The second (and more complicated) will provide a method to add values to the list, in a way that their base addresses are never changed once they are added. This second method will likely do it's own memory management, as std::vector isn't designed for stuff like that -- but this is the primary reason for adding 'virtual' to the base interface in the first place: so the user can use one of the derived classes but still pass the base class handle to existing code that uses it.

Thanks again to everyone who replied, I guess I'm off to go write a bunch of dummy operator== functions now...

Share this post


Link to post
Share on other sites
1) You should have 'typename std::vector<T>::iterator iter = mList.begin();'. The language requires that, though not all compilers complain.

2) Don't write search loops like that yourself. Use std::find(). If you want to remove all matching instances instead of just the first, you can use std::remove() or std::remove_if().

Quote:
Original post by bpoint
Presently two extensions are planned. One will provide an insertion sort, placing the add()ed value into a proper position into the list.


3) You should be aware that insertion into a vector is O(N).

4) You could always just use std::sort to keep things sorted. You probably don't need to sort after every individual add().

Quote:
The second (and more complicated) will provide a method to add values to the list, in a way that their base addresses are never changed once they are added.


5) It is impossible to both do that, and keep elements in a contiguous chunk the way that std::vector does. When it's time to resize, the memory "in front of" the current allocation might simply not be available.

6) std::list already does that.

Quote:
but this is the primary reason for adding 'virtual' to the base interface in the first place: so the user can use one of the derived classes but still pass the base class handle to existing code that uses it.


7) This kind of thing is normally handled with compile-time polymorphism, because there's no reason to make the decision at runtime about what container to use. To make code that can operate on any kind of container (or at least several different kinds) but which uses compile-time polymorphism, it's usual to template on an iterator type. (This is more flexible than templating on a container type and requesting the .begin() and .end() of the container; with the iterator template, you can operate on a portion of a container, or on an array.) See, for example, the standard library algorithms.

In short: you are trying to fight against design decisions made by the language standards committee when they standardized the standard library. These decisions were not made lightly. Please reconsider.

Share this post


Link to post
Share on other sites
Quote:
Original post by Zahlman
5) It is impossible to both do that, and keep elements in a contiguous chunk the way that std::vector does. When it's time to resize, the memory "in front of" the current allocation might simply not be available.


I never said the elements had to be in a single contiguous chunk. This array will use multiple chunks scattered around in memory, but with the use of operator[], the array can appear to the user as if it was contiguous.

In any case, I've hit a new brick wall. There are other functions in the original class that make use of other operators (such as > and +) that now are required once I added 'virtual' to the rest of the class. I can kinda deal with adding operator==, but I will absolutely not add all of these other operators to all of my existing classes when they simply just doesn't make sense.

To that end, the virtual plan is off. Some code in our existing code base will have to be re-worked to support the new derived class types directly, since we won't be able to use virtual to call to the derived class, but I guess that's just what C++ requires.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!