Sign in to follow this  

template trickery

This topic is 4204 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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?

Share this post


Link to post
Share on other sites
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<T,B>::get(void) const' is not a specialization of a function template
I would argue that it actually IS a specialization of a function template.

Share this post


Link to post
Share on other sites
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<int>()) or you can add a parameter which selects the function:

template<typename T> typename Trait<T>::Type get(T&) const;

Share this post


Link to post
Share on other sites
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<U>::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<U>::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<U>::Type get( const Derived<T,B>& d )
{
return d.B::template get<U>();
}
};

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;
}


Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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<int>() without relying on template argument deduction.

Share this post


Link to post
Share on other sites
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<int>() without relying on template argument deduction.


That's what I wrote in the last sentence.

Share this post


Link to post
Share on other sites
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).

Share this post


Link to post
Share on other sites
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 system
template<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 user
struct 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.

Share this post


Link to post
Share on other sites
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;
}

Share this post


Link to post
Share on other sites
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<>().

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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<name> 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 :-)

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites

This topic is 4204 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

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