C++ 字符串化模板参数

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1488186/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 20:10:20  来源:igfitidea点击:

Stringifying template arguments

c++templatesc-preprocessormetadatastringification

提问by sold

Is it possible in C++ to stringify template arguments? I tried this:

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

#define STRINGIFY(x) #x

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

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

But what I get is a 'T', and not an 'int'. Seems that the preprocessors kicks in before template resolution.

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

Is there any other way to do this?

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

Is there any way for the preprocessing to take place after template resolution? (Compiler is VC++).

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

回答by eduffy

You could try

你可以试试

 typeid(T).name()

Edit: Fixed based on comments.

编辑:根据评论修正。

回答by Cat Plus Plus

You could use some template magic.

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

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

This has an advantage over RTTI (i.e. typeinfo) - it is resolved during compilation; and disadvantage - you need to provide type information yourself (unless there is some library that does that already that I'm not aware of; maybe something in Boost even).

这比 RTTI 有优势(即typeinfo) - 它在编译期间解决;和缺点 - 您需要自己提供类型信息(除非有一些我不知道的库已经这样做了;甚至可能在 Boost 中)。

Or, as Matrin Yorksuggested in comments, use inline function templates instead:

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

But, if you'll ever need to store more information about that particular type, then class templates will probably be better.

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

回答by Gui Prá

Your code doesn't work because the preprocessor, responsible for searching and expanding the macros you use in your code, is not aware of the language itself. It is just a text parser. It finds that STRINGIFY(T) in the very function template and expand it, much before you give a type to that template. As it turns out, you will always get "T" instead of the typename you expected, unfortunately.

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

As litbsuggested, I've (badly) implemented this `getTypeName' function template that returns the typename you pass it:

正如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;
}

The code above results in the following output with GCC flag -s ("strip all symbols from binary") enabled:

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

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

So, you see, getTypename() does a fairly better job, at the cost of that fugly string parsing hack (I KNOW, it's damn ugly).

所以,你看, getTypename() 做得相当好,代价是那次糟糕的字符串解析技巧(我知道,这太丑了)。

A few points to take into account:

需要考虑的几点:

  • The code is GCC only. I don't know how to port it to another compiler. Probably only a few others have such a facility to produce so pretty function names, and from what I searched, MSVC++ doesn't have one, if you're asking yourself that.
  • If, in a new version, GCC formats __PRETTY_FUNCTION__'s differently, the string matching can break and you'll have to fix it. For this same reason I also warn that getTypeName() mightbe good for debugging (and, still, maybe not even good for that), but it is surelybad, bad, and bad for other purposes such as comparing two types in a template or something like that (I don't know, just guessing what someone might think of..). Use it solely for debugging, and preferentially don't call it in release builds (use macros to disable), so that you don't use __PRETTY_FUNCTION__and thus the compiler doesn't produce the string for it.
  • I'm definitely no expert, and I'm not sure whether some odd type could cause the string matching to fail. I'd like to ask for people who read this post to comment if they know of such a case.
  • The code uses a static std::string. It means that, if some exception is thrown from its constructor or destructor, there is no way that it will reach a catch block and you'll get an unhandled exception. I don't know whether std::strings can do that, but beware that, if they do, you're potentially in trouble. I used it because it needs a destructor to free the memory. You could implement your own class for that, though, ensuring no exception is thrown besides allocation failure (that's pretty much fatal, isn't it? So...), and return a simple C-string.
  • With typedefs you can get some weird results, like this (for some reason, the site breaks the formatting of this snippet, so I'm using this paste link): http://pastebin.com/f51b888ad
  • 该代码仅适用于 GCC。我不知道如何将它移植到另一个编译器。可能只有少数其他人有这样的工具来生成如此漂亮的函数名称,并且从我搜索的内容来看,MSVC++ 没有,如果你问自己的话。
  • 如果在新版本中 GCC 格式__PRETTY_FUNCTION__不同,则字符串匹配可能会中断,您必须修复它。出于同样的原因,我还警告说 getTypeName()可能有利于调试(并且,仍然可能甚至不利于调试),但对于其他目的(例如比较模板中的两种类型)来说肯定是糟糕的、糟糕的和糟​​糕的或类似的东西(我不知道,只是猜测某人可能会想到什么......)。仅将它用于调试,并且优先不要在发布版本中调用它(使用宏禁用),这样您就不会使用__PRETTY_FUNCTION__,因此编译器不会为它生成字符串。
  • 我绝对不是专家,我不确定某些奇怪的类型是否会导致字符串匹配失败。我想请阅读这篇文章的人发表评论,如果他们知道这种情况。
  • 该代码使用静态 std::string。这意味着,如果从其构造函数或析构函数抛出某个异常,则它无法到达 catch 块,并且您将获得未处理的异常。我不知道 std::strings 是否可以做到这一点,但要注意,如果他们做到了,您可能会遇到麻烦。我使用它是因为它需要一个析构函数来释放内存。不过,您可以为此实现自己的类,确保除了分配失败之外不会抛出任何异常(这非常致命,不是吗?所以...),并返回一个简单的 C 字符串。
  • 使用 typedefs,您可以获得一些奇怪的结果,如下所示(由于某种原因,该站点破坏了此代码段的格式,因此我使用了此粘贴链接):http: //pastebin.com/f51b888ad

Despite those disadvantages, I'd like to say that it sure is fast. For the second time you lookup for one same type name, it will cost picking a reference to a global std::string containing the name. And, comparatively to the template specialiazation methods suggested before, there is nothing else you have to declare besides the very template itself, so it is really much easier to use.

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

回答by David Rodríguez - dribeas

No, you cannot work on types as if they were variables. You could write code that extracted the typeid() of an element and printed the name, but the resulting value will probably not be what you expect (type names are not standarized).

不,您不能像处理变量一样处理类型。您可以编写提取元素的 typeid() 并打印名称的代码,但结果值可能不是您所期望的(类型名称未标准化)。

You can also work with template specializations (and some macro magic) to achieve a more interesting version if the number of types you want to work with is limited:

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

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
}

Or you could even combine both versions: implement the printtype generic template using typeinfo and then provide specializations for the types you want to have fancier names.

或者您甚至可以将两个版本结合起来:使用 typeinfo 实现打印类型通用模板,然后为您想要拥有更漂亮名称的类型提供特殊化。

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

回答by T.E.D.

This breaks one of my primary tenets of C++ code writing: Avoid using tricks in both the template features and the preprocessor at the same time.

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

Part of the reason for templates and the nastiness they introduce into the language was an attempt to wean developers away from using the preprocessor. If you use both, then the terrorists win.

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

回答by Hayden

If you use boost/core/demangle.hpp, you can get a reliable human-readable string.

如果您使用 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;

回答by Andreas Otto

in my code I use the "awful" double-declaration of the "Class-Name"

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

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

because c++ is NOT able to extract the string "MyServer" from the template… the only "way" to get "rid" of this… using a cpp "wrapper"

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

#define MQ_CPPSTR(s) #s
#define MqFactoryCAdd(T) MqFactoryC<T>::Add(MQ_CPPSTR(T)).Default()

回答by fish2000

Here's what I do: I have a demangle()function (implemented on top of abi::__cxa_demangle()which I call with a couple of convenience template function overloads, nameof(), with either the type I want stringified or an instance of same.

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

It's fairly compact, so I'll reproduce it here in all its glory. In demangle.hhwe have:

它相当紧凑,所以我将在这里重现它的所有辉煌。在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 */

… And then in demangle.cpp:

......然后在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 */

To use this, I think you'll have to link to libc++(or whatever your local equivalent is) to use abi::__cxa_demangle(). What may be suboptimal for the OP is the fact that this does the demangling and stringification at runtime. I'd personally love something constexpr-friendly in leu of this, but since I suffer from a severe macro-abuse allergy, I find this to be the least generally-unreasonable solution to this problem.

要使用它,我认为您必须链接到libc++(或任何本地等效项)才能使用abi::__cxa_demangle(). 对于 OP 而言,可能不是最理想的事实是,它在运行时进行了分解和字符串化。我个人喜欢constexpr在 leu 中友好的东西,但由于我患有严重的宏观滥用过敏症,我发现这是解决这个问题的最不合理的解决方案。

(the terminatornamespace is inconsequential – I use this code in a libunwind-based stacktracer called from termination handler – feel free to s///gthat token)

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