Подтвердить что ты не робот

Строка аргументов шаблона

Возможно ли в С++ форматировать аргументы шаблона? Я пробовал это:

#define STRINGIFY(x) #x

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

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

Но я получаю "T", а не "int". Кажется, что препроцессоры пинают до разрешения шаблона.

Есть ли другой способ сделать это?

Есть ли способ предварительной обработки после разрешения шаблона? (Компилятор - VС++).

4b9b3361

Ответ 1

Вы можете попробовать

 typeid(T).name()

Изменить. Исправлено на основе комментариев.

Ответ 2

Вы можете использовать магию шаблонов.

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

Но, если вам когда-либо понадобится хранить больше информации об этом конкретном типе, тогда, вероятно, будут лучше использовать шаблоны классов.

Ответ 3

Ваш код не работает, потому что препроцессор, отвечающий за поиск и расширение макросов, которые вы используете в своем коде, не знает сам язык. Это всего лишь текстовый синтаксический анализатор. Он обнаруживает, что 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 ( "strip all symbols from binary" ):

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

Итак, вы видите, getTypename() выполняет довольно хорошую работу за счет этой неуклюжей строки, обрабатывающей хак (я ЗНАЮ, это чертовски уродливо).

Несколько точек, которые необходимо учитывать:

  • Код - только GCC. Я не знаю, как перенести его в другой компилятор. Вероятно, только у немногих других есть такое средство для создания столь красивых имен функций, и из того, что я искал, MSVС++ не имеет этого, если вы спрашиваете себя.
  • Если в новой версии форматы GCC __PRETTY_FUNCTION__ по-разному, соответствие строк может сломаться, и вам придется исправить это. По этой же причине я также предупреждаю, что getTypeName() может быть хорошо для отладки (и, возможно, даже не для этого хорош), но это, безусловно, плохо, плохо и плохо для других целей, таких как сравнение двух типов в шаблоне или что-то в этом роде (я не знаю, просто догадываюсь, о чем кто-то может подумать..). Используйте его исключительно для отладки и предпочтительно не вызывайте его в сборках релизов (используйте макросы для отключения), так что вы не используете __PRETTY_FUNCTION__ и, следовательно, компилятор не создает для него строку.
  • Я определенно не эксперт, и я не уверен, может ли какой-то нечетный тип привести к сбою строки. Я хотел бы попросить людей, которые прочитали это сообщение, прокомментировать, знают ли они о таком случае.
  • В коде используется статический std::string. Это означает, что если какое-либо исключение выбрано из его конструктора или деструктора, нет способа, чтобы он достигло блока catch, и вы получите необработанное исключение. Я не знаю, может ли std:: strings это сделать, но будьте осторожны, если они это сделают, у вас могут быть проблемы. Я использовал его, потому что ему нужен деструктор, чтобы освободить память. Однако вы можете реализовать свой собственный класс для этого, не исключая исключения, кроме отказа от распределения (это довольно фатально, не так ли?)... и возвращает простую C-строку.
  • С typedefs вы можете получить некоторые странные результаты, например, это (по какой-то причине сайт нарушает форматирование этого фрагмента, поэтому я использую эту ссылку для вставки): http://pastebin.com/f51b888ad

Несмотря на эти недостатки, я хотел бы сказать, что это очень быстро. Во второй раз, когда вы ищете одно имя типа, это будет стоить выбора ссылки на глобальный std::string, содержащий имя. И, по сравнению с предложенными ранее шаблонами, вы ничего не должны объявлять, кроме самого самого шаблона, поэтому его гораздо проще использовать.

Ответ 4

Нет, вы не можете работать с типами, как если бы они были переменными. Вы можете написать код, который извлекал 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, а затем предоставить специализации для типов, которые вы хотите присвоить именам.

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

Ответ 5

Это нарушает один из моих основных принципов написания кода на С++: избегайте использования трюков как в функциях шаблона, так и в препроцессоре одновременно.

Часть причин для шаблонов и гадости, которые они вводили в язык, была попыткой отучить разработчиков от использования препроцессора. Если вы используете оба, то террористы победят.

Ответ 6

Если вы используете 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;

Ответ 7

Вот что я делаю: у меня есть функция demangle() (реализована поверх abi::__cxa_demangle(), которую я вызываю с помощью нескольких перегрузок функций шаблонов функций nameof(), либо с типом, который я хочу, либо с строкой, или с экземпляром тот же.

Его довольно компактный, так что Ill воспроизводит его здесь во всей красе. В 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, - это тот факт, что во время выполнения он выполняет демаркинг и строчение. Id лично люблю что-то constexpr - дружелюбно в свете этого, но, поскольку я страдаю от серьезной аллергии на малоберцовое насилие, я считаю это наименее общепринятым решением этой проблемы.

(пространство имен terminator является несущественным - я использую этот код в стекедрайве на основе libunwind, вызванном из обработчика завершения - не стесняйтесь s///g этот токен)