如何使用类型特征进行条件编译?

9

我正在尝试编写像这里所示的代码,但使用C++11特性,而不使用Boost。

根据这个例子,我尝试定义一个response_trait,并基于特征的结果进行条件编译。如何使这个工作?

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

请问您能否将问题自包含并描述您想要实现的目标? - Kerrek SB
1
它是自包含的。如果 T 有一个 .normal 成员,则 response_traithas_normal 应该为 true,并且应该选择正确的编译路径。 - bobobobo
你的if和else分支不是完全错了吗:如果没有条件就使用普通if? - tauran
5
你不能使用预处理器指令来实现这个,因为traits是一个C++编译时的概念,与预处理器没有任何关系。 - Some programmer dude
1
此外,您可以直接使用 http://en.cppreference.com/w/cpp/types/is_same 来处理 VertexN。 - tauran
显示剩余2条评论
2个回答

16
您可以尝试类似这样的内容:
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>());
}

您只需要稍微修改一下您的特征:

#include <type_traits>
template <typename> struct response_trait : std::false_type { };
template <> struct response_trait<VertexN> : std::true_type { };

纯模板特化有优势吗? - tauran
1
@tauran:这不就是普通的模板特化吗?我有什么遗漏的吗?还是你指的是重载函数?函数模板并不太喜欢特化。 - Kerrek SB
@Kerrek SB: 我的意思只是将转换函数(在“Model”之外)变成一个函数而不是一个方法,并进行特殊化。但现在我看到,如果你想保持方法,你的答案是完美的。 - tauran
请注意,在 XCode 4.4、C++11 编译中,您必须在行 transform_impl(m, typename response_trait<T>()); 中去掉 typename 才能使其正常工作。但是该代码在 VS2012 中可以正常运行。 - bobobobo
@bobobobo:哦,绝对的,谢谢。那只是我的错误。我太习惯写 typename trait<T>::type 了,有时候会失误... - Kerrek SB

1

如果您的代码可以放入函数中而不会使设计变得笨重(例如,当您需要访问对象的许多成员变量时),这里有一个替代方案。当然,有时候专门化整个类是更可取的。

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

这非常聪明。我没有想过使用全局函数而不是成员函数。也许这就是STL将其函数作为全局函数(例如std::find等)的原因。 - bobobobo
2
事实证明,这种方式有点繁琐,它将鼓励模板专门化比使用类型特征更多的类型。考虑一下如果您有几个更多的顶点格式,VertexNC(带法线、颜色的顶点),VertexNTC(带法线、纹理坐标、颜色的顶点)。您必须为每个带有法线的Vertex类型进行模板专门化,而不是在类型特征中设置hasNormal标志打开或关闭。 - bobobobo
这取决于情况。在这种情况下,我也会选择另一种解决方案 :) - tauran

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接