字符串化模板参数

时间:2009-09-28 17:04:44

标签: c++ templates c-preprocessor metadata stringification

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

#define STRINGIFY(x) #x

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

int main() 
{
     Stringify<int> s;
}

但我得到的是'T',而不是'int'。似乎预处理器在模板解析之前就开始了。

还有其他办法吗?

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

7 个答案:

答案 0 :(得分:32)

你可以尝试

 typeid(T).name()

修改:根据评论修正。

答案 1 :(得分: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;
}

这比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;

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

答案 2 :(得分: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;
}

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

float
void (*)(int, int)
f
PFviiE

所以,你看,getTypename()做得相当好,代价是解析黑客的那个乱码(我知道,这真是太可怕了)。

要考虑几点:

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

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

答案 3 :(得分: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
}

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

template <typename T>
const char* printtype()
{
   return typeid(T).name();
}

答案 4 :(得分:5)

这破坏了我编写C ++代码的主要原则之一:避免同时在模板功能和预处理器中使用技巧。

模板的部分原因以及它们引入语言的肮脏是试图让开发人员远离使用预处理器。如果你同时使用两者,那么恐怖分子就会获胜。

答案 5 :(得分:2)

如果您使用boost / core / demangle.hpp,您可以获得可靠的人类可读字符串。

char const * name = typeid(T).name();
boost::core::scoped_demangled_name demangled( name );

std::cout << (demangled.get() ? demangled.get() : "Failed to demangle") << std::endl;

答案 6 :(得分:0)

这就是我所做的:我有一个demangle()函数(在abi::__cxa_demangle()之上实现,我使用一些便利模板函数重载调用nameof(),使用类型I想要字符串化或相同的实例。

它相当紧凑,所以我会在这里重现它的所有荣耀。在demangle.hh我们有:

#pragma once
#include <typeinfo>

namespace terminator {

    /// actual function to demangle an allegedly mangled thing
    char const* demangle(char const* const symbol) noexcept;

    /// convenience function template to stringify a name of a type,
    /// either per an explicit specialization:
    ///     char const* mytypename = terminator::nameof<SomeType>();
    template <typename NameType>
    char const* nameof() {
        try {
            return demangle(typeid(NameType).name());
        } catch (std::bad_typeid const&) {
            return "<unknown>";
        }
    }

    ///  … or as implied by an instance argument:
    ///     char const* myinstancetypename = terminator::nameof(someinstance);
    template <typename ArgType>
    char const* nameof(ArgType argument) {
        try {
            return demangle(typeid(argument).name());
        } catch (std::bad_typeid const&) {
            return "<unknown>";
        }
    }

} /* namespace terminator */

...然后在demangle.cpp

#include "demangle.hh"

#include <cstdlib>
#include <cxxabi.h>
#include <mutex>
#include <memory>

namespace terminator {

    namespace {

        /// define one singular, private, static std::mutex,
        /// to keep the demangler from reentering itself
        static std::mutex mangle_barrier;

        /// define a corresponding private and static std::unique_ptr,
        /// using a delete-expression to reclaim the memory malloc()'ed by
        /// abi::__cxa_demangle() upon its return.
        /// … we use clang pragmas to add flags locally for this to work:
        #pragma clang diagnostic push
        #pragma clang diagnostic ignored "-Wglobal-constructors"
        #pragma clang diagnostic ignored "-Wexit-time-destructors"
        std::unique_ptr<char, decltype(std::free)&> demangled_name{ nullptr, std::free };
        #pragma clang diagnostic pop

    }

    char const* demangle(char const* const symbol) noexcept {
        if (!symbol) { return "<null>"; }
        std::lock_guard<std::mutex> lock(mangle_barrier);
        int status = -4;
        demangled_name.reset(
            abi::__cxa_demangle(symbol,
                                demangled_name.get(),
                                nullptr, &status));
        return ((status == 0) ? demangled_name.release() : symbol);
    }

} /* namespace terminator */

要使用此功能,我认为您必须链接到使用libc++的{​​{1}}(或当地的等效内容)。 OP可能不是最理想的事实是它在运行时执行demangling和stringification。我个人喜欢abi::__cxa_demangle()的一些事情 - 对此感到友好,但由于我患有严重的宏观滥用过敏症,我发现这是解决这个问题的最不合理的解决办法。

constexpr命名空间是无关紧要的 - 我在基于libunwind的堆栈跟踪器中使用此代码从终止处理程序调用 - 随意terminator该令牌)