tags:

views:

409

answers:

5

Important: This question is getting quite long, if this is the first time you're reading this I suggest you start near the bottom as the solution is there in a round about way, but the code is a bit smelly.

After reading a tutorial on templates, I was able to change my existing class to support generic types. However, many objects are already dependent on this, so I'm looking for a way of making a method generic rather than the entire class.

I've tried the following, but it looks like this behavior isn't supported.

// foobar1.h
// Don't want the entire class to be generic.
//template<class T>
class FooBar1
{
public:
    template<class T> T Foo();
}

// foobar2.h
class FooBar2 : public FooBar1
{
}

// foobar1.cpp
template<class T>
T FooBar1::Foo()
{
    return something;
}

// test.cpp
FooBar1 fb1;
FooBar2 fb2 = fb1.Foo<FooBar2>();

Is this supposed to not work, or is it a bug elsewhere that I'm getting confused with?

undefined reference to FooBar2 Foo<FooBar2>()

To put this in to some sort of perspective as to what I want to achieve, here's how I'd do it in C#...

pubic class FooBar1
{
    public T Foo<T>()
        where T : FooBar1
    {
        return something;
    }
}

public class FooBar2 : FooBar1 { }

FooBar1 fb1 = new FooBar1();
FooBar2 fb2 = fb1.Foo<FooBar2>();

Is there any way I can do something similar to that in C++?

Update 1:

Just corrected some minor syntax details (I meant to make Foo public, and return T, not FooBar2). Still getting compiler error... When I remove the template behavior the error goes away, the answer so far say what I'm doing is valid... but if it is then why am I getting the error still? Thanks for your answers!

Update 2:

Josh, here's the actual source code (well, what I think is relevant, anwyay - let me know if you think I've skipped an important bit).

// ImageMatrix.h
class ImageMatrix : public VImage
{
public:
    // ... various functions ...
    template<class T> T GetRotatedCopy(VDouble angle);
}

// ImageFilter.h
class ImageFilter : public ImageMatrix
{
    // ... various functions ...
}

// ImageMatrix.cpp
template<class T>
T ImageMatrix::GetRotatedCopy(VDouble angle)
{
    // ... create a new instance of ImageMatrix and return it.
}

// ImageProcessor.cpp
ImageFilter filter2 = filterPrototype.GetRotatedCopy<ImageFilter>(90);

And here's the actual compiler error:

/home/nick/Projects/ViMRID/vimrid/Debug/libvimrid.so: undefined reference to `vimrid::imaging::processing::ImageFilter vimrid::imaging::ImageMatrix::GetRotatedCopy(double)'

Update 3:

By the way, everything but the implementation line is located in a library; so it's being called from a separate binary... Does this matter? Correction; its all in the same library. All blocks are different files though.

Update 4:

When I comment out the implementation line (ImageFilter filter2 = filterPrototype...) it builds fine, so it seems to be this line thats causing it...

Update 5 (Solved?):

Still having problems... Could this be a problem with the namespaces? Scratch that, OK, I've grasped the concept of templates now! :) The template definition must be in the header along with the declaration (right?) - so now that I've moved the declaration in to ImageMatrix.h, everything compiles. However, I've had to use dynamic_cast to get it working; is this right? If I'm way off please correct me!

// This is in the header file!
// Help!!! This looks really really smelly...
template<class T>
T ImageMatrix::GetRotatedCopy(VDouble angle)
{
    ImageMatrix image = _getRotatedCopy(angle);
    ImageMatrix *imagePtr = &image;
    return *dynamic_cast<T*>(imagePtr);
}

Update 6:

Refering to update 5, when I don't use dynamic_cast...

template<class T>
T ImageMatrix::GetRotatedCopy(VDouble angle)
{
    ImageMatrix image = _getRotatedCopy(angle);
    ImageMatrix *imagePtr = &image;
    //return *dynamic_cast<T*>(imagePtr);
    return *imagePtr;
}

... I get this error ...

../src/imaging/processing/../ImageMatrix.h: In member function ‘T vimrid::imaging::ImageMatrix::GetRotatedCopy(vimrid::VDouble) [with T = vimrid::imaging::processing::ImageFilter]’:
../src/imaging/processing/ImageProcessor.cpp:32:   instantiated from here
../src/imaging/processing/../ImageMatrix.h:45: error: conversion from ‘vimrid::imaging::ImageMatrix’ to non-scalar type ‘vimrid::imaging::processing::ImageFilter’ requested
make: *** [src/imaging/processing/ImageProcessor.o] Error 1

Update 7:

Also, if I don't use all of that smelly code in update 6...

class ImageMatrix : public VImage
{
public:
    template<class T> T GetRotatedCopy(VDouble angle);
private:
    ImageMatrix _getRotatedCopy(VDouble angle);
};

template<class T>
T ImageMatrix::GetRotatedCopy(VDouble angle)
{
    return _getRotatedCopy(angle);
}

... I get the same error as in update 6.

+9  A: 

Yup, you were pretty close, try this:

class FooBar1
{
public:
    template<class T> T Foo();
};

class FooBar2 : public FooBar1
{
};

template<class T>
T FooBar1::Foo()
{
    return T();
}

int main()
{
   FooBar1 fb1;
   FooBar2 fb2 = fb1.Foo<FooBar2>();
}

The issue you were having is that you were specifying the return type of FooBar1::Foo() as FooBar2, you should have it as just T.

If you want to do specific things for FooBar2, you can specialize the on FooBar2:

template<>
FooBar2 FooBar1::Foo<FooBar2>()
{
    return FooBar2();
}

Edit: It sounds like you are having issues with the compiler not finding the definition for your templated GetRotatedCopy. Templates in C++ are rather finicky, and the usual practice is to put the entire template implementation in a header file. You might try this:

class ImageMatrix : public VImage
{
public:
    // ... various functions ...
    template<class T> T GetRotatedCopy(VDouble angle)
    {
       // ... create a new instance of ImageMatrix and return it.
    }
};

Edit: I can't find the gcc documentation, but here's microsoft's documentation on explicit template instantiation and libraries, it gives a bit of an idea of what's happening. You'll likely want to either include the implementation in the header as I suggested earlier, or call GetRotatedCopy in the library, or else instantiate it explicitly in the library. See veefu's answer below for the syntax.

The reason this works differently from C#, is that templates in C++, unlike C#, actually create a whole new class/function for each different combination of template parameters. e.g. vector<int> is a completely different class (with a different set of compiled methods) than vector<string>. See Kevin's answer for a better explanation.

As to the error going away when you don't use the template, that doesn't actually tell you much, since until you actually instantiate a template, it's not going to

RE Update 5,6,7

Your dynamic_cast isn't going to work, you can only use it if the pointer is actually pointing to an instance of the class you are casting to. (It works similar to the as operator in C#).

I suspect now, that what you are wanting is the CRTP. You start out with an instance of ImageFilter, and want to use a base class method on it, and get back a new copy of ImageFilter. Try something along these lines:

template <class T>
class ImageMatrix
{
public:
    T GetRotatedMatrix()
    {
     return T();
    }
};

class ImageFilter : public ImageMatrix<ImageFilter>
{
};

int main()
{
    ImageFilter filterPrototype;
    ImageFilter otherFilter = filterPrototype.GetRotatedMatrix();
}

Otherwise, if you really do want to start out with an ImageMatrix and transform it into an ImageFilter, you'll have to add a constructor on ImageFilter that takes an ImageMatrix.

Eclipse
Nice comprehensive answer. +1.
j_random_hacker
Ah, that was a typo, it was meant to return T - sorry for the confusion; I'm still getting the compiler error.
nbolton
You'll have to provide more code then, the example I gave you compiles just fine.
Eclipse
@r3n: You need to make Foo() public
Martin York
@Martin York: Oops, yeah the method in my actual app is public; this was just a quick snippet I threw together... Still getting compiler error...
nbolton
@r3n: can you post an actual example that gives the issue you are having?
Eclipse
@r3n: Are you compiling this example directly in a single file? If you've got the template function implementation in a separate file the compiler could be having problems generating it. Try including the template code in your header file.
veefu
@Josh: Done! :) ... @veefu: Not sure what you mean, each block of code is in a separate file.
nbolton
@Josh: Please see updated #3, maybe this is relevant?
nbolton
@r3n: See here: http://www.cplusplus.com/doc/tutorial/templates/ under the section titled "Templates and multiple-file projects" This may be part of the problem.
veefu
+3  A: 

The error you are getting may be due to other problems in your sample code. Here is an example of templated member and free functions that compiles fine with G++.

// Don't want the entire class to be generic.
//template<class T>
class FooBar1
{
public:
    template<class T> T Foo();
};

class FooBar2 : public FooBar1
{
};

template<class T>
T FooBar1::Foo()
{
    return T();
}

template <class T>
T FreeFunction()
{
    return T();
}

int main()
{
    FooBar1 fb1;
    FooBar2 fb2 = fb1.Foo<FooBar2>();

    FooBar2 fb3 = FreeFunction<FooBar2>();
}

In C++, you generally include both the declaration and definition of templated classes in the header file. Doing this ensures that the compiler generates code for the templated function or class with the specific template arguments filled in at compile time.

It is possible to put the declaration in a header file and the definition in a cpp file. This works fine if you have a small number of known types you are templating. It is necessary to explicitly instantiate the class or function in the cpp file with the types you expect it to be used with, so that the compiler can generate the code correctly.

Header file:

template <class T>
T DoSomething();

Cpp file:

template <class T>
T DoSomething();
{
    return T();
}

template FooBar1 DoSomething<FooBar1>();
template FooBar2 DoSomething<FooBar2>();
mch
@mch: Does it compile because its in the same file? What happens if you split it up into headers?
nbolton
@r3n I've expanded the text to answer your question.
mch
+1  A: 

Update 3 and 4 give it away, I think, but it's difficult to tell without knowing your project layout.

If you're trying to expose templates from a library to be called from another location, you have to either

A: include the template definition in the header file of your library

or

B: explicitly instantiate the template code in the library, so that later code will have an implementation to link to

template<class T>
T ImageMatrix::GetRotatedCopy(VDouble angle)
{
  // ... create a new instance of ImageMatrix and return it.
}
// Add the following line
template ImageFilter ImageMatrix::GetRotatedCopy<ImageFilter>(VDouble);

I think that should resolve the problem.

veefu
Sorry, not in a different library, I got confused. 4 different files though.
nbolton
It doesn't matter. If the usage of the template function is in a different compilation unit (.cpp file) than the implementation, you have to do A or B above.
veefu
@veefu: Thanks, where should I add "template ImageFilter ImageMatrix..." tried after the function, and after the ImageMatrix declaration, and before the implementation. All produce compiler errors...
nbolton
You know, what might be useful is starting with a simple implementation, like you example, get it to compile, then start separating out the elements into separate files to match the real code. Diagnose the problem with example code, then adapt the fix to your real code.
veefu
+4  A: 

Adding on to what veefu said, templates in C++ are NOT like C# generics (the topic is tagged as C# so I assume you are comparing them somewhat). In C++, the actual code is not generated at runtime, but only at compile-time, so that if you have a class with a template in it, you either must have it in a header file, or you must instantiate EXACTLY which instances of it exist, or else you'll (usually) get a linker error because it can't FIND what you're looking for, because it was never actually made. When doing templates, the compiler actually makes as many "copies" of your template class as you have instantiated different "kinds" of the class. So from the STL, if you have Vector<int>, Vector<String>, and Vector<char>, the compiler actually outputs the code for 3 different classes. This is why templated classes are almost-always defined in header files and not in compiled libraries, because the compiler needs to generate what you're using.

This is in contrast to Generics in C# (and Java IIRC) where references are used, and you can only use what you have specified the generic to be inherited from, or from object. You need to declare that something implements IComparable to use any of the methods from that, or whatever other restrictions you put on it. Essentially, when you use a generic, it's a compile-time-trick to ensure type safety, but doesn't ACTUALLY compile-in the class. C++ is different, in that if you have a class with a templated field, then depending on the size of that class, the resultant class will be bigger or smaller, which affects the actual size of the object.

I hope that made some sense, though it was a bit long. I actually didn't know you could do templated functions as mch showed.

Kevin
+1  A: 

May it be that

template<class T>
T FooBar1::Foo()
{
    return something;
}

is in a .cpp file other than the calling file? If so this would lead to your error. The implementation must be available in the same compilation unit (.cpp file + all its includes) as the call.

So normally template implementations are put in the header file where the declaration lies.

Sou you should check this.

rstevens
Hmm, so I should move the template into the header?
nbolton