Ответ 1
Вы можете попробовать
typeid(T).name()
Изменить. Исправлено на основе комментариев.
Возможно ли в С++ форматировать аргументы шаблона? Я пробовал это:
#define STRINGIFY(x) #x
template <typename T>
struct Stringify
{
Stringify()
{
cout<<STRINGIFY(T)<<endl;
}
};
int main()
{
Stringify<int> s;
}
Но я получаю "T", а не "int". Кажется, что препроцессоры пинают до разрешения шаблона.
Есть ли другой способ сделать это?
Есть ли способ предварительной обработки после разрешения шаблона? (Компилятор - VС++).
Вы можете попробовать
typeid(T).name()
Изменить. Исправлено на основе комментариев.
Вы можете использовать магию шаблонов.
#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;
Но, если вам когда-либо понадобится хранить больше информации об этом конкретном типе, тогда, вероятно, будут лучше использовать шаблоны классов.
Ваш код не работает, потому что препроцессор, отвечающий за поиск и расширение макросов, которые вы используете в своем коде, не знает сам язык. Это всего лишь текстовый синтаксический анализатор. Он обнаруживает, что 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() выполняет довольно хорошую работу за счет этой неуклюжей строки, обрабатывающей хак (я ЗНАЮ, это чертовски уродливо).
Несколько точек, которые необходимо учитывать:
__PRETTY_FUNCTION__
по-разному, соответствие строк может сломаться, и вам придется исправить это. По этой же причине я также предупреждаю, что getTypeName() может быть хорошо для отладки (и, возможно, даже не для этого хорош), но это, безусловно, плохо, плохо и плохо для других целей, таких как сравнение двух типов в шаблоне или что-то в этом роде (я не знаю, просто догадываюсь, о чем кто-то может подумать..). Используйте его исключительно для отладки и предпочтительно не вызывайте его в сборках релизов (используйте макросы для отключения), так что вы не используете __PRETTY_FUNCTION__
и, следовательно, компилятор не создает для него строку.Несмотря на эти недостатки, я хотел бы сказать, что это очень быстро. Во второй раз, когда вы ищете одно имя типа, это будет стоить выбора ссылки на глобальный std::string, содержащий имя. И, по сравнению с предложенными ранее шаблонами, вы ничего не должны объявлять, кроме самого самого шаблона, поэтому его гораздо проще использовать.
Нет, вы не можете работать с типами, как если бы они были переменными. Вы можете написать код, который извлекал 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();
}
Это нарушает один из моих основных принципов написания кода на С++: избегайте использования трюков как в функциях шаблона, так и в препроцессоре одновременно.
Часть причин для шаблонов и гадости, которые они вводили в язык, была попыткой отучить разработчиков от использования препроцессора. Если вы используете оба, то террористы победят.
Если вы используете 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;
Вот что я делаю: у меня есть функция 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
этот токен)