9

I'm trying to write code like here but using C++11 features, without Boost.

Working from this example, I tried to define a response_trait, and basee conditional compilation on the result of the trait. How can I make this work?

#include <vector>
using namespace std ;

struct Vector{ float x,y,z ; } ;
struct Vertex { Vector pos ; } ;
struct VertexN { Vector pos, normal ; } ;
struct Matrix {} ;

template <typename T>
struct response_trait {
  static bool const has_normal = false;
} ;

template <>
struct response_trait<VertexN> {
  static bool const has_normal = true;
} ;

template <typename T>
struct Model
{
  vector<T> verts ;

  void transform( Matrix m )
  {
    for( int i = 0 ; i < verts.size() ; i++ )
    {
      #if response_trait<T>::has_normal==true
      puts( "Has normal" ) ;
      // will choke compiler if T doesn't have .normal member
      printf( "normal = %f %f %f\n", verts[i].normal.x, verts[i].normal.y, verts[i].normal.z ) ;
      #else
      puts( "Doesn't have normal" ) ;
      printf( "pos = %f %f %f\n", verts[i].pos.x, verts[i].pos.y, verts[i].pos.z ) ;
      #endif
    }
  }

} ;

int main()
{
  Matrix m ;
  Model<Vertex> model ;
  model.verts.push_back( Vertex() ) ;
  model.transform( m ) ;

  Model<VertexN> modelNormal ;
  modelNormal.verts.push_back( VertexN() ) ;
  modelNormal.transform( m ) ;
}
Community
  • 1
  • 1
bobobobo
  • 64,917
  • 62
  • 258
  • 363
  • Can you please make your question self-contained and describe what you're trying to achieve? – Kerrek SB Dec 09 '12 at 12:51
  • 1
    It is self contained. `#if` `T` has a `.normal` member, the `response_trait` `has_normal` should be true, and the correct compilation path should be chosen. – bobobobo Dec 09 '12 at 12:53
  • Unless I've completely misunderstood type traits. The linked question was my starting point, but I have no idea if I've taken it the wrong way. – bobobobo Dec 09 '12 at 12:54
  • Isn't your if and else branch exactly the wrong way: you use normal if it has none? – tauran Dec 09 '12 at 12:55
  • @tauran You are correct, fixed. – bobobobo Dec 09 '12 at 12:56
  • 5
    You can't use the preprocessor directives for this, as traits is a C++ compile-time concept and does not involve the preprocessor at all. – Some programmer dude Dec 09 '12 at 12:57
  • 1
    Additionally you can use http://en.cppreference.com/w/cpp/types/is_same with VertexN directly. – tauran Dec 09 '12 at 13:00

2 Answers2

16

You could try something like this:

void transform_impl(Matrix const & m, std::true_type const &)
{
    // has normal
}

void transform_impl(Matrix const & m, std::false_type const &)
{
    // doesn't have normal
}

template <typename T>
void transform(Matrix const & m)
{
    transform_impl(m, response_trait<T>());
}

You just need to modify your trait a bit:

#include <type_traits>
template <typename> struct response_trait : std::false_type { };
template <> struct response_trait<VertexN> : std::true_type { };
hiddensunset4
  • 5,825
  • 3
  • 39
  • 61
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • Is there an advantage to plain template specialization? – tauran Dec 09 '12 at 12:58
  • 1
    @tauran: This *is* plain template specialization, isn't it? Am I missing something? Or are you referring to the overloaded functions? Function templates don't like specializations very much... – Kerrek SB Dec 09 '12 at 13:00
  • @Kerrek SB: I just meant making transform a function (outside of `Model`) instead of a method and specialize that. But now I see that your answer is perfect if you want to keep methods. – tauran Dec 09 '12 at 13:06
  • Just a note you have to take _out_ `typename` in the line `transform_impl(m, typename response_trait());` for this to work in XCode 4.4, C++11 compilation. But the code works as is in VS2012. – bobobobo Dec 09 '12 at 18:42
  • @bobobobo: Oh, absolutely, thank you. That was just an error on my part. I'm so used to writing `typename trait::type` that it sometimes slips in... – Kerrek SB Dec 09 '12 at 23:13
1

Here is an alternative solution if your code can be put into functions without making your design cumbersome (e.g. when you need access to a lot of member variables of your object). Of course sometimes it is preferrable to specialize the whole class.

#include <vector>
#include <stdio.h>

using namespace std ;

struct Vector{ float x,y,z ; } ;
struct Vertex { Vector pos ; } ;
struct VertexN { Vector pos, normal ; } ;
struct Matrix {} ;

template <typename T>
void printVertex(T vert)
{
      printf( "Doesn't have normal" ) ;
      printf( "pos = %f %f %f\n", vert.pos.x, vert.pos.y, vert.pos.z ) ;
}

template <>
void printVertex(VertexN vert)
{
      printf( "Has normal" ) ;
      printf( "normal = %f %f %f\n", vert.normal.x, vert.normal.y, vert.normal.z ) ;
}

template <typename T>
struct Model
{
  vector<T> verts ;

  void transform( Matrix m )
  {
    for( int i = 0 ; i < verts.size() ; i++ )
    {
        printVertex(verts[i]);
    }
  }
} ;

int main()
{
  Matrix m ;
  Model<Vertex> model ;
  model.verts.push_back( Vertex() ) ;
  model.transform( m ) ;

  Model<VertexN> modelNormal ;
  modelNormal.verts.push_back( VertexN() ) ;
  modelNormal.transform( m ) ;
}
tauran
  • 7,986
  • 6
  • 41
  • 48
  • That is very clever. I didn't think about making global functions instead of member functions. Perhaps this is why the STL has its functions as global functions (`std::find`, etc.) – bobobobo Dec 09 '12 at 18:14
  • 2
    It turns out this way is a bit more cumbersome, it will encourage template specialization of more types than using type traits. Consider if you have a couple of more vertex formats, `VertexNC` (vertex with normal, color), `VertexNTC` (vertex with normal, texcoord, color). You'd have to template specialize _every_ `Vertex` type with a normal in it, instead of setting the `hasNormal` flag on or off in the type traits. – bobobobo Dec 09 '12 at 18:23
  • It depends on the situation. In this case I would pick the other solution too :) – tauran Dec 09 '12 at 19:28