Sign in to follow this  
Bombshell93

[C++] setting a struct type in a method & selective template

Recommended Posts

I'm running through a tutorial and after completing a chapter going back over the code, changing it to my needs.
At the moment I'm changing the way a model is handled.

In the Model is a struct type representing the Vertex information type.
I have created a list of structs holding all sorts of vertex data and they are all inheriting from a base struct Vertex holding the position variable.

What I want is to have a specific struct for inheriting vertex types inside the model class,
Then I want 2 constructors, 1 that gives it a default inheritance and 1 that allows selection of the inheritance.
pseudo code I'd think along the lines of
[code]
class ModelClass
{
struct VertexType;

ModelClass()
{
VertexType : BaseVertex;
};

ModelClass<BaseVertex T>() //by having BaseVertex the assumption here is that only BaseVertex and structs inheritting from it will be usable
{
VertexType : T;
}
};
[/code]

Is there any way I could do this?

Thanks in advanced,
Bombshell

Share this post


Link to post
Share on other sites
The only way you could do it would be to template the entire model class around the vertex data, which is probably the right thing to do anyways. Models with different vertex formats should not be type-compatible.

Share this post


Link to post
Share on other sites
thanks,
Do you know how I might limit the type to something descending from BaseVertex?
it'd be useful to stop people using types that may throw errors (this being intended for an engine)

EDIT:
I found this (as I only just found out about the problem with templates and implementation files)
[url="http://www.parashift.com/c++-faq-lite/templates.html#faq-35.12"]http://www.parashift....html#faq-35.12[/url]
and I'm curious about this line,
[code]template void foo<int>();[/code]
is this per function when using template classes?
Or can the template class be forward declared like this using the expected types?

Share this post


Link to post
Share on other sites
Explicitly instantiating templates has nothing to do with restricting what types you can specify as parameters to those templates.


I tend to prefer composition over inheritance for a number of reasons, so my usual solution to this is tagging. Basically, you want to define an empty inner struct in your valid vertex classes that tags them as, well, valid. Then you can use some parameter tricks in the template to ensure that this tag is present and that the template cannot be specialized for types that do not have the tag:

[source lang="cpp"]struct SomeData
{
int Foo;
float Bar;

SomeData(int a, float b)
: Foo(a), Bar(b)
{ }

struct Tag { };
};

template <typename T, T::Tag tag = T::Tag()>
struct SomeConsumer
{
T TheData;

explicit SomeConsumer(const T& value)
: TheData(value)
{ }

float Operation() const
{
return TheData.Bar;
}
};

struct InvalidData
{
float Bar;

explicit InvalidData(float value)
: Bar(value)
{ }
};

int main()
{
SomeConsumer<SomeData> this_will_work(SomeData(42, 3.14159f));
SomeConsumer<InvalidData> this_will_not_work(InvalidData(666));
}
[/source]

SomeData contains the magic tag, so it will compile cleanly. InvalidData provides a Bar member variable, so ordinarily SomeConsumer would happily accept it as a parameter because SomeConsumer only checks for the presence of a member variable named Bar. However, since InvalidData does not contain the magic tag, the specialization will fail to compile

(That's off the top of my head and it's late, so apologies if I bungled something and it doesn't work.)

Share this post


Link to post
Share on other sites
thanks for clarification on that, its the perfect solution :)

As for the forward declaration of types, I was more curious as a way of getting over the problem of not being able to use implementation files with template functions and classes.

Share this post


Link to post
Share on other sites
It's kind of nitpicky but also an important terminology issue: forward declaration is not the same thing as explicit template instantiation.

Forward declaration is about telling the compiler that a type is going to be fully defined later on at some point; fundamentally it exists as a means to resolve circular dependencies between types. It also has uses for interfaces and information hiding; you can forward declare a type in an API so that clients can use pointers/references of that type, but then never define the type in a way that is visible to external code, thereby preventing that code from knowing anything about the implementation details of the type. Really though forward declaration is a nasty workaround for the fundamentally brain-dead compilation model of C and (by extension) C++.

Explicit template instantiation is about going from an incomplete (not-yet-specialized) type to a complete one. Remember that templates in C++ are not code; they are code [i]generators[/i]. A template doesn't do anything until it is instantiated with a suitable set of parameters. At that point, the compiler generates code that corresponds to the template with the parameters substituted into it. The problem is that, in order to generate all of the filled-out code, the compiler has to have "seen" the entire template implementation content.

The reason you can't use template implementations in an isolated code file (more properly, a translation unit) is that templates aren't code - they're just a way to generate code. When you put template implementations into a translation unit, you prevent the compiler from seeing those details in other translation units. The result is that when you go to use a specialized template from a [i]different[/i] translation unit, the compiler hasn't seen the implementation details - and therefore doesn't know how to generate the corresponding code.

The fix to this is explicit instantiation. Basically what this does is tell the compiler that it needs to generate all of the code corresponding to a particular template specialization, within the current translation unit. Now we've gone from just having a code generator to having actual code - and that code is now concretely implemented, so it is visible to the linker, and can be used from other, independent translation units.

The syntax for explicit instantiation is pretty easy. To borrow on the above example, all you need is to put your implementation details into a .cpp file (translation unit) and then instantiate them:

[code]template struct SomeConsumer<SomeData>;[/code]

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this