tags:

views:

78

answers:

4

So, I'm learning C++, and I've run into something which I know how to do in Java, but not in C++ :).

I have a template for a container object, which is defined as such:

template <class T>
class Container {
    vector<T> contained;

    public:

    void add(T givenObject) {
        this->contained.push_back(givenObject);
    }

    T get(string givenIdentifier) throw (exception) {
        for (int i = 0; i < this->contained.size(); i++) {
            if (this->contained[i].getIdentifier() == givenIdentifier) {
                return this->contained[i];
            }
        }
        throw new exception("An error has occured which has caused the object you requested to not be found. Please report this bug.");
    }

    bool empty() {
        return this->contained.empty();
    }

    bool identifierExists(string givenIdentifier) {
        for (int i = 0; i < this->contained.size(); i++) {
            if (this->contained[i].getIdentifier() == givenIdentifier) {
                return true;
            }
        }
        return false;
    }
};

This actually works very well, with one small issue. It comes down to two lines: the first is the template definition and the second is

this->contained[i].getIdentifer()

In Java, when declaring a Generic (template) one can define a superclass/interface which all members of T must extend in order to not create an error. However, I'm not sure of a way to do this in C++, and my concern is that coupling the implementation here to a getIdentifier method which might not be defined is bad design.

Now, it's not a huge deal if that's the case, this is just a little challenge project to help me learn the language, but I like to try to do things right. Is there a way to do what I'm thinking? I know you can do it with primitives, for instance:

template <int T>

is valid, but when I try to use a user defined class, I get a compiler error. Any suggestions?

+1  A: 

You don't have to do anything. If contained[i] doesn't have a getIdentifer() function, you will get a compile-time error (just as you would using an interface contrainst in Java, and just as you would outside of using templates).

To elaborate: If you were to write,

 int x = 10;
 long id = x.getIdentifer();

That would be considered a "bad design". It would just be a mistake, which the compiler will catch. That is exactly what will happen in you're example.

James Curran
+4  A: 

It's not possible for you to put artificial limitations on template type parameters. If the type given doesn't support the way you use it, you'll receive a compiler error. A feature called 'concepts,' which would essentially allow this, was going to be added to the next C++ standard, but it was delayed to the next-next standard due to time constraints. If T doesn't have a visible getIdentifier() function, the instantiation won't compile.

Template parameters need to be deduced at compile time. template<int T> is valid because the first template parameter is an integer; you can instantiate it with any constant integer. If you attempted to use it with a non-const integer variable, it wouldn't compile. An instance of a class isn't a compile time constant, so it can't be used.

dauphic
Wow, thanks for the very quick answers, guys. I appreciate that response, and I feel a lot better knowing that.
EricBoersma
A: 

C++ draws a pretty stark line between compile-time polymorphism (ie Templates) and run-time polymporphism (ie inheritance). So what you want to do is not supported by the language.

One typical practice to do what you are doing is to provide, in addition to T, a type that can get an identifier of a given T. This decouples the two behaviors into two types and you can specify (in English) the interface that must be implemented.

template <class T, class StringIdForT>
class Container 
{
  ...
   bool identifierExists(string givenIdentifier) 
   {
        StringIdForT idGetter;
        for (int i = 0; i < this->contained.size(); i++) 
        {
            if (idGetter.getIdentifier(this->contained[i]) == givenIdentifier) 
            {
                return true;
            }
        }
        return false;
   }  
};

You have a similar problem, StringIdForT must still define a specified method

Doug T.
+1  A: 

You've gotten a couple of other answers, both quite good (especially @dauphic's, IMO). I'd just add that the code you've given really looks an awful lot like quite an inefficient imitation of an std::map. Under most circumstances, std::map will probably work better. If you look up its interface, it'll also show you a way to decouple your container from having to specify getIdentifier() directly -- instead of directly using something like getIdentifier(), it uses a comparison functor that defaults to std::less<T>, which will (in turn) use T::operator< -- but you can also specify an entirely different comparison functor if you prefer.

I should also point out that while others have pointed out that you'll get a compile error if you use getIdentifier (or whatever) and attempt to instantiate over a class that doesn't supply it. I feel obliged to warn you, however, that the error message you get may be long, convoluted, and quite difficult to decipher. This is especially likely if there's some other type that does have a getIdentifier member available. In this case the error message you get say something like "Unable to to convert from type A to type B", where type A is whatever type you used to instantiate the container, and type B is whatever (often entirely unrelated) type that happens to have a getIdentifier member. What's happening is that the compiler sees that you've used getIdentifier, and sees that type B has that, so it tries to convert your type A object to a type B object, and finds that it can't, so that's what it tells you about in the error message.

P.S. Yes, I know this is really more of a comment than an answer. I apologize for that, but it won't really fit in a comment.

Jerry Coffin
Jerry; it's actually a great response, and incredibly useful for me as someone who's still trying to pick up the intricacies of C++. I really appreciate the information, and in an actual production product I would look into using Std::map, though in this case I feel like the implementation will be sufficient for what is, essentially a mental exercise.Seriously, thanks for the help, though.
EricBoersma