通过模板实现替换预处理器#ifdef块

时间:2016-09-09 10:19:36

标签: c++ templates template-meta-programming

我有很多代码,这些代码混杂着预处理程序块,例如#ifdef FEATURE_A#ifdef _MSC_VER等等。

我想重构一些代码,以便通过模板实现替换一些预处理器块。

修改 任务不是删除所有预处理程序块,而是删除其中的一些,以便摆脱混乱。

我不想让你厌倦了foobar的例子,所以这是现实世界中的一个(不是我的代码):

template <typename T>
std::string demangle()
{
#ifdef __GNUC__
  size_t sz;
  int status;
  char* ptr = abi::__cxa_demangle(typeid(T).name(), 0, &sz, &status);
  std::string name(ptr ? ptr : "", ptr ? strlen(ptr) : 0);
  if(ptr){ free(ptr); }
  std::string::size_type pos = name.rfind("::");
  if(pos != std::string::npos)
  {
    name = name.substr(pos + 2);
  }  
#elif _MSC_VER
  std::string name(typeid(T).name());
  static const std::string struct_prefix("struct ");
  static const std::string class_prefix("class ");
  static const std::string ptr_postfix(" *");

  std::string::size_type
  at = name.find(struct_prefix);
  if(at != std::string::npos) { name.erase(at, struct_prefix.size()); }
  at = name.find(class_prefix);
  if(at != std::string::npos) { name.erase(at, class_prefix.size()); }
  at = name.find(ptr_postfix);
  if(at != std::string::npos) { name.erase(at, ptr_postfix.size()); }
#else
  std::string name(typeid(T).name());
#endif
  return name;
}

问题1 :如何将其转换为等价模板实现?

问题2 :为什么值得付出努力,为什么不呢?

2 个答案:

答案 0 :(得分:2)

这是不可能的。 C ++模板使用两阶段查找(Two phase name lookup for C++ templates - Why?),因此函数模板中使用的任何不依赖于模板参数的名称必须在声明时可用。

您的GCC实现使用名称abi::__cxa_demangle,因此任何不提供该名称的实现都应拒绝您的代码(有些可能不会,但仅仅因为它们没有正确实现两阶段查找:{{3 }})。

解决此问题的唯一方法是在预处理器abi::__cxa_demangle块中包含#ifdef的使用,这无论如何都有效地暗示了使用原始实现。

答案 1 :(得分:1)

<强>更新 正如其他人所指出的那样,所提供的真实世界的例子以漂亮的方式展示它,有些情况下,不可能摆脱预处理器。特别是当PP涵盖像abi::__cxa_demangle这样的平台特定时。 但是,我是模板元编程的新手,所以要找出这种方法的优点和缺点,这是一个有趣的问题。

这是我自己的解决方案,不幸的是它几乎是原始代码的3倍。这主要是因为解决方案需要一些帮助。

问题1:

我使用枚举和Int2Type映射器将预处理器值转换为用户定义的类型。在第二步中,将不同的部分提取为部分专用模板。

这是重构的代码示例(版本2):

#include <iostream>
#include <cstring>
#include <stdlib.h>
#include <typeinfo>


enum CompilerIds 
{
    ANY = 0,
    MSC = 1,
    GNUC = 2
};

template <int v>
struct Int2Type 
{   
    const static int value= v; 
};

#ifdef __GNUC__
 #include <cxxabi.h>
 typedef Int2Type<GNUC> CompilerType;
#elif _MSC_VER
 namespace abi
 {
    char* __cxa_demangle(const char* name, int n, size_t* sz, int* status);
 }
 typedef Int2Type<MSC> CompilerType;
#else
 typedef Int2Type<ANY> CompilerType;
#endif

template <int N> 
struct compiler_traits
{
    static std::string demangle(std::string name)
    {

        return name;
    }
};

template <>
struct compiler_traits<GNUC> 
{
    static std::string demangle(std::string name)
    {
        size_t sz;
        int status;
        char* ptr = abi::__cxa_demangle(name.c_str(), 0, &sz, &status);
        std::string retName(ptr ? ptr : "", ptr ? strlen(ptr) : 0);
        if(ptr){ free(ptr); }
        std::string::size_type pos = retName.rfind("::");
        if(pos != std::string::npos)
        {
            retName = retName.substr(pos + 2);
        }
        return retName;
    }
};

template <>
struct compiler_traits<MSC >
{
    static std::string demangle(std::string name)
    {
        static const std::string struct_prefix("struct ");
        static const std::string class_prefix("class ");
        static const std::string ptr_postfix(" *");

        std::string::size_type
        at = name.find(struct_prefix);
        if(at != std::string::npos) { name.erase(at, struct_prefix.size()); }
        at = name.find(class_prefix);
        if(at != std::string::npos) { name.erase(at, class_prefix.size()); }
        at = name.find(ptr_postfix);
        if(at != std::string::npos) { name.erase(at, ptr_postfix.size()); }

        return name;
    }
};

template <typename T, typename traits = compiler_traits<CompilerType::value> >
struct demangle
{
    static std::string Exec()
    {
        return traits::demangle(typeid(T).name());
    }
};


int main() 
{
    std::cout << "\n mangled:" << typeid(Int2Type<GNUC>).name();
    std::cout << "\n demangled:" << demangle<Int2Type<GNUC> >::Exec() <<"\n";

    std::cout << "\n instatiate the msc version:" << compiler_traits<MSC>::demangle("struct Int2Type<2>") <<"\n";

    return 0;
}

gcc 4.9.2的输出是:

mangled:8Int2TypeILi2EE
demangled:Int2Type<2>

instatiate the msc version:Int2Type<2>

我不得不使用MS编译器的前向声明来破解abi::__cxa_demangle(在生产代码中永远不会这样做)。您将看到,MS系统上compiler_traits<GNUC>的实例化将失败。

问题2:

原则上可以用模板替换PP块,但也可能有一些严重的缺点。

我仍然倾向于给它一个机会,这是值得的,特别是当你没有“平台的东西”,但功能切换的东西,如#ifdef FEATURE_A等等。

使用模板:

  • 扩展非常简单,不会破坏OCP
  • 它提高了可读性,特别是如果你有很多#ifdef的
  • 其他无法访问的代码变得可测试