字符串化模板参数

sol*_*old 34 c++ templates metadata stringification c-preprocessor

在C++中是否可以对模板参数进行字符串化?我试过这个:

#define STRINGIFY(x) #x

template <typename T>
struct Stringify
{
     Stringify()
     {
          cout<<STRINGIFY(T)<<endl;
     }
};

int main() 
{
     Stringify<int> s;
}
Run Code Online (Sandbox Code Playgroud)

但我得到的是'T',而不是'int'.似乎预处理器在模板解析之前启动.

有没有其他方法可以做到这一点?

有没有办法在模板解析后进行预处理?(编译器是VC++).

edu*_*ffy 34

你可以试试

 typeid(T).name()
Run Code Online (Sandbox Code Playgroud)

编辑:根据评论修复.

  • 请记住,编译器不一定要为`name()`赋予一个含义返回值,但大多数都是. (6认同)
  • 这应该是`typeid()`,而不是`typeinfo()` - 后者是标题`<typeinfo>`的名称,而`std :: type_info`是`typeid()返回的对象的类类型`. (5认同)
  • 天哪,`typeid(T).name()` 的输出是_丑陋的!_我刚刚尝试过。名字被毁了!我正在使用 clang 编译器。那不能满足我的需要。我需要它是一个最美丽的 C 字符串,而不是一个名字被破坏的名字。 (2认同)

Cat*_*lus 23

你可以使用一些模板魔法.

#include <iostream>

template <typename T>
struct TypeName { static const char *name; };

template <typename T>
const char *TypeName<T>::name = "unknown";

template <>
const char *TypeName<int>::name = "int";

template <typename T>
struct Stringify
{
     Stringify()
     {
          std::cout << TypeName<T>::name << std::endl;
     }
};

int main() 
{
     Stringify<int> s;
}
Run Code Online (Sandbox Code Playgroud)

这比RTTI(即typeinfo)更有优势- 它在编译期间得到解决; 和缺点 - 你需要自己提供类型信息(除非有一些我已经不知道的那个库;甚至可能在Boost中提供一些东西).

或者,正如Matrin York在评论中建议的那样,使用内联函数模板:

template <typename T>
inline const char* typeName(void) { return "unknown"; }

template <>
inline const char* typeName<int>(void) { return "int"; }

// ...
std::cout << typeName<T>() << std::endl;
Run Code Online (Sandbox Code Playgroud)

但是,如果您需要存储有关该特定类型的更多信息,那么类模板可能会更好.

  • 而不是建立变量.构建返回相应字符串的内联函数.那么你就不会遇到这种方法可能出现多重定义的问题. (2认同)

Gui*_*ira 16

您的代码不起作用,因为负责搜索和扩展您在代码中使用的宏的预处理器不了解语言本身.它只是一个文本解析器.它在函数模板中找到STRINGIFY(T)并将其展开,远在为该模板提供类型之前.事实证明,不幸的是,你总是得到"T"而不是你期望的类型名.

正如litb建议的那样,我(很糟糕地)实现了这个`getTypeName'函数模板,它返回你传递它的类型名:

#include <iostream>

template <typename _Get_TypeName>
const std::string &getTypeName()
{
    static std::string name;

    if (name.empty())
    {
        const char *beginStr = "_Get_TypeName =";
        const size_t beginStrLen = 15; // Yes, I know...
                                       // But isn't it better than strlen()?

        size_t begin,length;
        name = __PRETTY_FUNCTION__;

        begin = name.find(beginStr) + beginStrLen + 1;
        length = name.find("]",begin) - begin;
        name = name.substr(begin,length);
    }

    return name;
}

int main()
{
    typedef void (*T)(int,int);

    // Using getTypeName()
    std::cout << getTypeName<float>() << '\n';
    std::cout << getTypeName<T>() << '\n'; // You don't actually need the
                                           // typedef in this case, but
                                           // for it to work with the
                                           // typeid below, you'll need it

    // Using typeid().name()
    std::cout << typeid(float).name() << '\n';
    std::cout << typeid(T).name() << '\n';

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

上面的代码导致以下输出启用GCC标志-s("从二进制中删除所有符号"):

float
void (*)(int, int)
f
PFviiE
Run Code Online (Sandbox Code Playgroud)

所以,你看,getTypename()做了一个相当好的工作,代价是解析hack的那个乱码(我知道,这真是太可怕了).

需要考虑以下几点:

  • 代码仅限GCC.我不知道如何将它移植到另一个编译器.可能只有少数其他人有这样的设施可以产生如此漂亮的功能名称,而且根据我搜索的内容,如果你问自己,那么MSVC++就没有.
  • 如果在新版本中,GCC格式__PRETTY_FUNCTION__不同,字符串匹配可能会中断,您必须修复它.出于同样的原因,我也警告getTypeName()可能对调试有好处(并且,甚至可能甚至不好),但对于其他目的,例如比较模板中的两种类型,它肯定是坏的,坏的和坏的或类似的东西(我不知道,只是猜测某人可能会想到的......).仅将它用于调试,并且优先不在发布版本中调用它(使用宏来禁用),这样就不会使用__PRETTY_FUNCTION__,因此编译器不会为它生成字符串.
  • 我绝对不是专家,我不确定某些奇怪的类型是否会导致字符串匹配失败.如果他们知道这种情况,我想请读这篇文章的人发表评论.
  • 该代码使用静态std :: string.这意味着,如果从构造函数或析构函数抛出一些异常,它将无法到达catch块并且您将获得未处理的异常.我不知道std :: strings是否可以做到这一点,但要注意,如果他们这样做,你可能会遇到麻烦.我使用它是因为它需要一个析构函数来释放内存.你可以为它实现自己的类,但是,确保除了分配失败之外不会抛出任何异常(这非常致命,不是吗?所以......),并返回一个简单的C字符串.
  • 使用typedef你可以得到一些奇怪的结果,比如这样(出于某种原因,该网站打破了这个片段的格式,所以我使用这个粘贴链接):http://pastebin.com/f51b888ad

尽管有这些缺点,但我想说它确实很快.第二次查找同一个类型名称时,将花费选择对包含该名称的全局std :: string的引用.而且,与之前建议的模板特化方法相比,除了模板本身之外没有其他任何东西需要声明,因此它实际上更容易使用.

  • 写你对 `strlen` 的评论,为什么不使用 `const char beginStr[] = "_Get_TypeName =";` 这将允许你使用 `sizeof`,除非它衰减到一个指针。 (2认同)

Dav*_*eas 12

不,你不能在类型上工作,就好像它们是变量一样.您可以编写提取元素的typeid()并打印名称的代码,但结果值可能不是您所期望的(类型名称不是标准化的).

如果您要使用的类型数量有限,您还可以使用模板特化(以及一些宏魔术)来实现更有趣的版本:

template <typename T> const char* printtype(); // not implemented

// implement specializations for given types
#define DEFINE_PRINT_TYPE( type ) \
template<>\
const char* printtype<type>() {\
   return #type;\
}
DEFINE_PRINT_TYPE( int );
DEFINE_PRINT_TYPE( double );
// ... and so on
#undef DEFINE_PRINT_TYPE
template <typename T> void test()
{
   std::cout << printtype<T>() << std::endl;
}
int main() {
   test<int>();
   test<double>();
   test<float>(); // compilation error, printtype undefined for float
}
Run Code Online (Sandbox Code Playgroud)

或者你甚至可以组合两个版本:使用typeinfo实现printtype泛型模板,然后为想要拥有更高级名称的类型提供特化.

template <typename T>
const char* printtype()
{
   return typeid(T).name();
}
Run Code Online (Sandbox Code Playgroud)