Skip to main content
GameDev.net gamedev.net
🔒 Locked

template trickery

Started by hplus0603 Jun 14, 2006 at 2:22 AM 13 replies 2.1k views
Original Post
hplus0603
hplus0603
Check out this code, which compiles fine:


template<typename T> struct Trait {
  typedef T Type;
};

struct Base {
  template<typename T> typename Trait<T>::Type get() const;
};

template<typename T> struct Derived : public Base {
  template<> typename Trait<T>::Type get<T>() const { return 0; }
};

Now contrast to this code, which gives a "; missing before <" error in the struct Derived definition of get():


template<typename T> struct Trait {
  typedef T Type;
};

struct Base {
  template<typename T> typename Trait<T>::Type get() const;
};

template<typename T, typename B> struct Derived : public B {
  template<> typename Trait<T>::Type get<T>() const { return 0; }
};

I can sort-of see why -- the compiler doesn't know that I intend to pass a "base" in as type B, so it doesn't know that get<>() is an explicit specialization. How can I work around this, other than adding Base as an explicit base class to the Derived template?
enum Bool { True, False, FileNotFound };
hplus0603
hplus0603
I should add that the following also doesn't work:

template<typename T> struct Trait {  typedef T Type;};struct BaseBase {  template<typename T> typename Trait<T>::Type get() const;};struct Base : public BaseBase {};template<typename T, typename B> struct Derived : public B, public BaseBase {  template<> typename Trait<T>::Type get<T>() const { return 0; }};


The error I get (from MSVC 2005) is:
error C2912: explicit specialization; 'const float &Derived::get(void) const' is not a specialization of a function template
I would argue that it actually IS a specialization of a function template.
enum Bool { True, False, FileNotFound };
nmi
nmi
Neither version of your code compiles with gcc 4.1.0.

The function declaration
template<typename T> typename Trait<T>::Type get() const;

won't work, since the return value is not part of the function signature.
Did you actually try to call/instantiate this function ?
For instance:
void foo(){	Base bi;	int ib = bi.get();	Base bf;	float fb = bf.get();	Derived<int> di;	int id = di.get();	Derived<float> df;	float fd = df.get();}


The get() function of the Derived class will work, since there is just one version generated which depends on the template type of the Derived class.
The get() function of the Base class will not work, since many versions of this function may be generated and the return value is not used to select one. Either you can explicitly tell the compiler which function to use (i.e. get()) or you can add a parameter which selects the function:
template<typename T> typename Trait<T>::Type get(T&) const;

Fruny
Fruny
I don't think it can be done directly, but this works as I think you want. I'm sure that some light sprinkling of Boost might help clean it a bit.

#include <iostream>template<typename T> struct Trait {  typedef T Type;};struct Base {    template<typename U> typename Trait::Type get() const    {        return 1;    }};template<typename T, typename B, typename U> struct helper;template<typename T, typename B> struct Derived : public B{    template<typename U> typename Trait::Type get() const    {        return helper<T,B,U>::get(*this);    }    typename Trait<T>::Type get_T() const { return 0; }};template<typename T, typename B, typename U> struct helper{    static typename Trait::Type get( const Derived<T,B>& d )    {        return d.B::template get();    }};template<typename T, typename B> struct helper<T,B,T>{    static typename Trait<T>::Type get( const Derived<T,B>& d )    {        return d.get_T();    }};int main(){    Derived<int,Base> x;    std::cout << x.get<int>() << std::endl;    std::cout << x.get<double>() << std::endl;}
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
Fruny
Fruny
OK, the C++ Standard explicitely [rolleyes] disallow what you want to do in clause 14.7.3-18:

Quote:
In an explicit specialization declaration for a member of a class template or a member template that appears in namespace scope, the member template and some of its enclosing class templates may remain unspecialized, except that the declaration shall not explicitely specialize a class member template if its enclosing class templates are not explicitely specialized as well. ...


I think it works when you specify Base as the base class, because then you are working with a member of Base, which isn't a template.
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
Fruny
Fruny
Quote:
Original post by nmi
Neither version of your code compiles with gcc 4.1.0.

The function declaration
template<typename T> typename Trait<T>::Type get() const;

won't work, since the return value is not part of the function signature.


The return type isn't part of the signature, but the template parameters still allow to discriminate. You can explicitely call get() without relying on template argument deduction.
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
nmi
nmi
Quote:
Original post by Fruny
The return type isn't part of the signature, but the template parameters still allow to discriminate. You can explicitely call get() without relying on template argument deduction.


That's what I wrote in the last sentence.
hplus0603
hplus0603
Thanks Fruny. That doesn't quite do what I want, though.

What I want to do is create structures with metadata to get around the lack of introspection and reflection in C++. The types that I really want to specialize on are the "member names" of the struct. The "helper" route doesn't quite get me there (although I'll cook on it some more).
enum Bool { True, False, FileNotFound };
hplus0603
hplus0603
Here's a solution to what I actually want to do. I want to declare "members" as type names, and declare structs as aggregates of these members. This allows me to do compile-time reflection to do things like generate visitor for marshalling, etc.

// Declare my "member types" (this can be macro-ized)template<typename T> struct Traits;struct foo;template<> struct Traits<foo> {  typedef unsigned short Type;};struct bar;template<> struct Traits<bar> {  typedef float Type;};// Declare helpers in the systemtemplate<typename N> struct Member {  typename Traits<N>::Type _memb;};struct Base {};template<typename N, typename B = Base> struct Instance : B, Member<N> {  template<typename T> typename Traits<T>::Type const & get() const {    return Member<T>::_memb;  }  template<typename T> void set(typename Traits<T>::Type const & t) {    Member<T>::_memb = t;  }};// Declare an actual structure used by a userstruct X :  public Instance<foo, Instance<bar> >{};int main(int argc, char* argv[]){  X x;  x.set<foo>(10):  x.set<bar>(20);  x.get<foo>();  x.get<bar>();  return 0;}


So, I'm now happy again.
enum Bool { True, False, FileNotFound };
Polymorphic OOP
Polymorphic OOP
Quote:
Original post by hplus0603
Here's a solution to what I actually want to do. I want to declare "members" as type names, and declare structs as aggregates of these members.

Sounds like you want a Boost.Fusion map, which was formally accepted into Boost in May and will be included with Boost in 1.34. For now, you can grab it here. Documentation is in the libs directory.

As an example, you can accomplish what you want in Boost.Fusion as:

#include <boost/fusion/sequence/container/map.hpp>struct foo;struct bar;int main(){  using namespace ::boost::fusion;  map< pair< foo, unsigned short >, pair< bar, float > > your_map;  at< foo >( your_map ) = 10;  at< bar >( your_map ) = 20;}
hplus0603
hplus0603
Thanks, but no. Using an std::map is between 100 and 10,000 times slower than using direct struct access. Not exactly what you want to put at the inner core of a massively multiplayer physics simulation, say :-) Also, a struct lets you lay out the order of members explicitly, which is important in many cases.

Really, I'm coming at this from the point of view of having used plain structs plus separately coded meta-data (functions or macros) to do marshalling for networking, but I want a simpler model that doesn't separate data (the struct members) and metadata (the marshalling and other visitors). I also don't want to use code generation (the traditional "solution" used in RPC).

Anyway, with a combination of my initial attempt and the inspiration of using type-specific access to get the member (thanks!) I'm all set.

For more reference, see this thread in the networking forum, where I initially suggested the approach in answer to a question, and then made sure that the sketch I suggested actually was implementable :-)


Edit: Ah, I didn't see that the "map" was a boost::fusion map, not a std::map. I think my solution is better for my needs, because boost::fusion::map uses a std::vector to store data (yeach!) and because it allows me to specify metadata (# of bits used in marshalling, etc) and explicit visitor policies. The traits actually get defined from macros, so the "real code" looks a lot simpler; my specific templates go in a header somewhere.

Not to mention I think get<>() and set<>() just reads more naturally than at<>().
enum Bool { True, False, FileNotFound };
Polymorphic OOP
Polymorphic OOP
Quote:
Original post by hplus0603
Edit: Ah, I didn't see that the "map" was a boost::fusion map, not a std::map. I think my solution is better for my needs, because boost::fusion::map uses a std::vector to store data (yeach!)

They don't use std::vectors to store data. Fusion maps are much like tuples and pairs, only instead of accessing elements by numbers or via explicit member access, you access them by type tags (along with all of the benefits of conforming Fusion container models and algorithms which allow them to be used more easily in generic code). Fusion maps do everything that your example shows and are just as efficient.
darkchrono4
darkchrono4
Not trying to hijack anything, but *nobody* can say that makes a program any more readable or easier. I thought I had a fairly decent grasp of the language but I have no clue what is going on.
hplus0603
hplus0603
Well; it uses a boost::fusion::vector for storage internally. After wading through the hundreds of headers, I agree: it does pretty much the same thing. Mine's a lot shorter, though (and I still like get better :-)

For reference, here's how I packed it up (the same example you posted):

#include <myheader.h>STRUCT_USHORT_BITS(foo,12);STRUCT_FLOAT_RES_LIMIT(bar,0.01,4000);int main(){  Struct<foo, Struct<bar> > my_map;  my_map.set<foo>(10);  my_map.set<bar>(20);}


So, mine's a few lines shorter, and compiles significantly faster (even with precompiled headers).

I admire everything they do in boost -- I just wish more of it was actually practically usable in everyday life. Having to do with anything from the readability of the code, to compile times, to the general understanding of the library among practitioners in the industry.

But that's a different thread :-)
enum Bool { True, False, FileNotFound };
Fruny
Fruny
Quote:
Original post by darkchrono4
Not trying to hijack anything, but *nobody* can say that makes a program any more readable or easier. I thought I had a fairly decent grasp of the language but I have no clue what is going on.


Template tricks like that generally end up forming the guts of a library. The code that uses that library is usually very readable. Making a library that is easy to use is not simple.
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.