Stringifying template arguments

前端 未结 8 1557
花落未央
花落未央 2020-12-04 17:00

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

#define STRINGIFY(x) #x

template 
struct Stringify
{
     Stringify()         


        
相关标签:
8条回答
  • 2020-12-04 17:07

    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.

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

    #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:

    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).

    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() might be good for debugging (and, still, maybe not even good for that), but it is surely bad, 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

    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.

    0 讨论(0)
  • 2020-12-04 17:20

    You could try

     typeid(T).name()
    

    Edit: Fixed based on comments.

    0 讨论(0)
  • 2020-12-04 17:20

    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).

    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.

    template <typename T>
    const char* printtype()
    {
       return typeid(T).name();
    }
    
    0 讨论(0)
  • 2020-12-04 17:21

    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).

    Or, as Martin York suggested in comments, use inline function templates instead:

    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.

    0 讨论(0)
  • 2020-12-04 17:21

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

    char const * name = typeid(T).name();
    boost::core::scoped_demangled_name demangled( name );
    
    std::cout << (demangled.get() ? demangled.get() : "Failed to demangle") << std::endl;
    
    0 讨论(0)
  • 2020-12-04 17:21

    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"

    #define MQ_CPPSTR(s) #s
    #define MqFactoryCAdd(T) MqFactoryC<T>::Add(MQ_CPPSTR(T)).Default()
    
    0 讨论(0)
提交回复
热议问题