46

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

#include <iostream>
#define STRINGIFY(x) #x
 
template <typename T>
struct Stringify
{
     Stringify()
     {
          std::cout << STRINGIFY(T) << endl;
     }
};
 
int main() 
{
     Stringify<int> s;
}

但我得到的是一个T,而不是一个int。似乎在模板实例化之前评估了预处理器宏。

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

模板实例化后有什么方法可以进行预处理吗?(编译器是VC++)。

4

8 回答 8

38

你可以试试

 typeid(T).name()

编辑:根据评论修复。

于 2009-09-28T17:07:13.663 回答
27

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

#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 中)。

或者,正如Martin 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;

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

于 2009-09-28T17:16:05.820 回答
18

您的代码不起作用,因为负责搜索和扩展您在代码中使用的宏的预处理器不知道语言本身。它只是一个文本解析器。它在函数模板中找到 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 字符串。
  • 使用 typedefs 你可以得到一些奇怪的结果,像这样(由于某种原因,该网站打破了这个片段的格式,所以我使用这个粘贴链接): http: //pastebin.com/f51b888ad

尽管有这些缺点,但我想说它确实很快。第二次查找一个相同的类型名称时,需要选择对包含该名称的全局 std::string 的引用。而且,与之前推荐的模板专业化方法相比,除了模板本身之外,您无需声明其他任何内容,因此使用起来确实容易得多。

于 2009-09-28T17:23:15.507 回答
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();
}
于 2009-09-28T17:09:02.527 回答
4

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

模板和它们引入语言的讨厌的部分原因是试图让开发人员远离使用预处理器。如果你同时使用两者,那么恐怖分子就赢了。

于 2009-09-28T17:43:51.420 回答
4

如果你使用 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;
于 2017-05-01T21:20:19.327 回答
3

在我的代码中,我使用“类名”的“糟糕”双重声明

MqFactoryC<MyServer>::Add("MyServer").Default();

因为c ++无法从模板中提取字符串“MyServer”......唯一的“方式”来“摆脱”这个......使用cpp“包装器”

#define MQ_CPPSTR(s) #s
#define MqFactoryCAdd(T) MqFactoryC<T>::Add(MQ_CPPSTR(T)).Default()
于 2018-02-27T09:30:22.893 回答
1

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

它相当紧凑,所以我将在这里重现它的所有荣耀。在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++(或任何本地等效项)才能使用abi::__cxa_demangle(). 对于 OP 来说可能不是最理想的是这样一个事实,即它在运行时进行了拆解和字符串化。我个人喜欢这种constexpr对 leu 友好的东西,但由于我患有严重的宏观滥用过敏症,我发现这是解决这个问题的最不普遍不合理的解决方案。

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

于 2016-05-17T09:17:11.933 回答