1

Let's say I have a template:

template <class N, class I>
void add(N* element, std::list<N*> & container, I (N::*f)() const,
        std::string successmsg, std::string exceptmsg) {
//...
}

And I want to call it for a list of Base Class pointers to a derivative class.

add(newAirplane, airplanes, &Airplane::getRegistration,
        "Added!", "Error: Existent!");

Airplane inherits from AirplaneType.

Of course, it doesn't compile, N is first defined as AirplaneType and then as Airplane.

I added a virtual getRegistration @ AirplaneType but of course, the compiler gives out a vtable error.

What's the proper way to solve this? AirplaneType has no registration attribute and I'm not interested in it having one. I also wanted to avoid virtual getRegistration() const {return "";}

Any suggestions for good practice?

EDIT:

Thanks for answers, but still not working. I think I have found the remaining problem, but not its solution:

void Airline::addAirplane(AirplaneType* airplane) {
add(newAirplane, airplanes, &Airplane::getRegistration,
        "Added!", "Error: Existent!");

}

The type of pointer received is AirplaneType, not Airplane.

airplanes is a list of AirplaneType pointers.

F. P.
  • 5,018
  • 10
  • 55
  • 80
  • ... and who thought up this C++ thing? bleh. – tidwall Nov 04 '10 at 00:57
  • 2
    Related discussion on C++ template covariance: http://stackoverflow.com/questions/639248/c-covariant-templates – Ben Jackson Nov 04 '10 at 01:04
  • 1
    If the pointer is only `AirplaneType*`, then what are you doing trying to call a function of `Airplane` on it? If the referand must be an instance of `Airplane`, you can `static_cast` the pointer. If it might not be, then if `AirplaneType` has any virtual functions you could `dynamic_cast`, but what do you plan to do in cases when the referand isn't an instance of `Airplane`? – Steve Jessop Nov 04 '10 at 01:29
  • That's what I'm asking. I need the template to be flexible enough to take `add(T*, list, I* N::f*)` (for N derived of T) and `add(T*, list, I T::f*)`. – F. P. Nov 04 '10 at 01:35
  • 1
    @Francisco: The remaining problem is nothing to do with the template. You can't call `f` on `element` if `f` isn't a member function of `element`. Never mind the template, *C++* isn't flexible enough to do it. What if your function `AddAirplane` is passed a pointer to a `Helicopter`, where `Helicopter` derives from `AirplaneType`, but not `Airplane`? What's supposed to happen when `add` needs to call `Airplane::getRegistration` on that `Helicopter`. If that's impossible, and in fact the `AirplaneType*` must point to an `Airplane`, then ideally `addAirplane` should take an `Airplane*`. – Steve Jessop Nov 04 '10 at 01:37
  • But can't it be done with virtuals, perhaps? The bad solution i thought out? – F. P. Nov 04 '10 at 01:38
  • 1
    @Francisco: Sure, if you make `getRegistration` a virtual function of `AirplaneType` then you're fine, because you can call it on any `AirplaneType*`. Then you no longer need the extra type in the template. – Steve Jessop Nov 04 '10 at 01:40
  • 1
    @Francisco: if `getRegistration` is only present on `Airplane`, but you need to be able to get a registration for any `AirplaneType*`, then you have a fundamental problem which can't be solved by messing with your template. The problem is that *not all AirplaneType objects provide the information that you need*. You either need to work out what to do with objects that don't provide it, or you need to tighten the interface so that you're only passed objects which do provide it. – Steve Jessop Nov 04 '10 at 01:46
  • I don't think I am explaining myself properly. Airplane has the desired registration. The one I want to know. However, for the template to work, I must implement a virtual dummy @ airplaneType (return "") so, when Airplane::getRegistration is called on a AirplaneType* Airplane pointer it "redirects" to the correct function. Can this be done? Sorry for confusion. – F. P. Nov 04 '10 at 01:46
  • 1
    Maybe not. What does the `add` function actually do with `f`? I've been assuming that it calls it on `element`, but if not then maybe there's some other workaround. From here, though, it just looks as though your interface isn't designed right. – Steve Jessop Nov 04 '10 at 01:48
  • 2
    @Francisco: Yes, it can be done. Pointers to virtual member functions work the way you want them to - the correct override is called even if both the object pointer and the member function pointer are of the base class type. I've edited my answer to demonstrate. – Steve Jessop Nov 04 '10 at 01:54
  • I am in love with you. Last question: My `Airline::addAirplane` really needs to take an AirplaneType * to an Airplane, not an Airplane * to an Airplane. Can't be done, right? – F. P. Nov 04 '10 at 01:56
  • Nevermind, found the answer myself. – F. P. Nov 04 '10 at 02:09
  • 1
    @Francisco: OK. I'd advise you to dig into why it is that you have this situation in the first place, though - it might be that you're working around a problem that really ought to be fixed elsewhere. – Steve Jessop Nov 04 '10 at 02:16

2 Answers2

2

You need to add an additional template parameter for the common base since C++ does not handle contravariant types. That is, std::list<Airplane*> is an entirely different type from std::list<AirplaneType*>, and no implicit conversion can occur from the list of pointers to the most derived to the least derived.. So, effectively your add function would need to become:

template <class N, class I, class B>
void add(N* element, std::list<B*> & container, I (N::*f)() const,
        std::string successmsg, std::string exceptmsg)
Nathan Ernst
  • 4,540
  • 25
  • 38
2

You need another template parameter, because you care about two different classes - the type of the pointer (and hence the member function you're going to call with it), and the type of the container:

#include <list>

struct AirplaneType {
};

struct Airplane : AirplaneType {
    int check() const { return 3; }
};

template <typename T, typename U, typename I>
void add(T* element, std::list<U*> & container, I (T::*f)() const) {
    container.push_back(element);
    I i = (element->*f)();
}

int main() {
    std::list<AirplaneType*> ls;
    Airplane a;
    add(&a, ls, &Airplane::check);
}

In this case my add function doesn't really use the fact that container is a list, so a more sensible version might be:

template <typename T, typename U, typename I>
void add(T* element, U & container, I (T::*f)() const) {
    container.push_back(element);
    I i = (element->*f)();
}

And then again, you could abstract further:

template <typename T, typename U, typename AUF>
void add(T element, U & container, AUF func) {
    container.push_back(element);
    typename AUF::result_type i = func(element);
}

... but that's slightly less convenient for the caller:

#include <functional>

add(&a, ls, std::mem_fun(&Airplane::check));

Any suggestions for good practice?

Don't create containers of raw pointers.

Edit: to get this working with a virtual function, with each of my options:

#include <list>
#include <functional>
#include <iostream>

struct AirplaneType {
    virtual int check() const { return 0; }
};

struct Airplane : AirplaneType {
    int check() const { std::cout << "check\n"; return 3; }
};

template <typename T, typename U, typename I>
void add(U* element, std::list<T*> & container, I (U::*f)() const) {
    container.push_back(element);
    I i = (element->*f)();
}

template <typename T, typename U, typename AUF>
void add2(T element, U & container, AUF func) {
    container.push_back(element);
    typename AUF::result_type i = func(element);
}

int main() {
    std::list<AirplaneType*> ls;
    Airplane a;
    add(static_cast<AirplaneType*>(&a), ls, &AirplaneType::check);
    add2(&a, ls, std::mem_fun(&AirplaneType::check));
}

Output is:

check
check

which shows that the override is correctly called even though the function pointer was taken to AirplaneType::check, not Airplane::check.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699