Ich schreibe einige Vorlagenklassen zum Parsen einiger Textdatendateien, und als solche ist es wahrscheinlich, dass die große Mehrheit der Analysefehler auf Fehler in der Datendatei zurückzuführen ist, die größtenteils nicht von Programmierern geschrieben wurden und daher benötigt werden eine nette Nachricht darüber, warum die App nicht geladen werden konnte, zB so etwas wie:
Fehler beim Parsen von example.txt. Der Wert ("notaninteger") des Schlüssels [MySectiom] ist kein gültiger int
Ich kann die Datei-, Abschnitts- und Schlüsselnamen aus den Argumenten ermitteln, die an die Vorlagenfunktion und die Mitgliedsvariablen in der Klasse übergeben wurden. Ich bin mir jedoch nicht sicher, wie ich den Namen des Typs ermitteln soll, in den die Vorlagenfunktion konvertieren möchte.
Mein aktueller Code sieht so aus, mit Spezialisierungen für einfache Zeichenfolgen und dergleichen:
template<typename T> T GetValue(const std::wstring §ion, const std::wstring &key)
{
std::map<std::wstring, std::wstring>::iterator it = map[section].find(key);
if(it == map[section].end())
throw ItemDoesNotExist(file, section, key)
else
{
try{return boost::lexical_cast<T>(it->second);}
//needs to get the name from T somehow
catch(...)throw ParseError(file, section, key, it->second, TypeName(T));
}
}
Ich muss lieber nicht für jeden Typ, den die Datendateien verwenden, bestimmte Überladungen vornehmen, da es viele davon gibt ...
Außerdem benötige ich eine Lösung, die keinen Laufzeitaufwand verursacht, es sei denn, eine Ausnahme tritt auf, dh ich möchte eine Lösung zur vollständigen Kompilierungszeit, da dieser Code tonnenweise aufgerufen wird und die Ladezeiten bereits etwas lang werden.
EDIT: Ok, das ist die Lösung, die ich mir ausgedacht habe:
Ich habe einen Typ, der Folgendes enthält
#pragma once
template<typename T> const wchar_t *GetTypeName();
#define DEFINE_TYPE_NAME(type, name) \
template<>const wchar_t *GetTypeName<type>(){return name;}
Dann kann ich das Makro DEFINE_TYPE_NAME verwenden, um CPP-Dateien für jeden Typ zu erstellen, mit dem ich mich befassen muss (z. B. in der CPP-Datei, die den Typ definiert, mit dem begonnen werden soll).
Der Linker kann dann die entsprechende Spezialisierung finden, solange sie irgendwo definiert wurde, oder andernfalls einen Linkerfehler auslösen, damit ich den Typ hinzufügen kann.
quelle
Antworten:
Die Lösung von Jesse Beder ist wahrscheinlich die beste, aber wenn Sie die Namen, die typeid Ihnen gibt, nicht mögen (ich denke, gcc gibt Ihnen beispielsweise verstümmelte Namen), können Sie Folgendes tun:
template<typename T> struct TypeParseTraits; #define REGISTER_PARSE_TYPE(X) template <> struct TypeParseTraits<X> \ { static const char* name; } ; const char* TypeParseTraits<X>::name = #X REGISTER_PARSE_TYPE(int); REGISTER_PARSE_TYPE(double); REGISTER_PARSE_TYPE(FooClass); // etc...
Und dann benutze es gerne
throw ParseError(TypeParseTraits<T>::name);
BEARBEITEN:
Sie können die beiden auch kombinieren,
name
in eine Funktion ändern , die standardmäßig aufgerufen wird,typeid(T).name()
und sich dann nur auf Fälle spezialisieren, in denen dies nicht akzeptabel ist.quelle
error: '#' is not followed by a macro parameter
Die Lösung ist
typeid(T).name()
Dies gibt std :: type_info zurück .
quelle
typeid(simd::double3x4).name() = "N4simd9double3x4E"
.typeid(simd::float4).name() = "Dv4_f"
C ++ 17, Xcode 10.1.typeid(T).name()
ist der kanonische Weg, dies zu tun, aber nur sehr wenige Compiler geben unverwirrte Namen zurück. Das einzige, mit dem ich persönlich vertraut bin, ist MSVC. Abhängig vom verwendeten Compiler besteht auch die Möglichkeit, dass einige Typinformationen zu Funktionstypen verloren gehen. Dies ist in diesem Fall jedoch wahrscheinlich irrelevant.typeid(T).name()
ist die Implementierung definiert und garantiert keine vom Menschen lesbare Zeichenfolge.Lesen von cppreference.com :
In einigen Fällen gibt gcc jedoch nicht die richtige Zeichenfolge zurück. Zum Beispiel habe ich auf meinem Computer gcc mit
-std=c++11
und innerhalb der Template-Funktiontypeid(T).name()
zurückgegeben"j"
für"unsigned int"
. Es ist so genannter verstümmelter Name. Verwenden Sie die Funktion abi :: __ cxa_demangle () (nur gcc), um einen echten Typnamen zu erhalten :#include <string> #include <cstdlib> #include <cxxabi.h> template<typename T> std::string type_name() { int status; std::string tname = typeid(T).name(); char *demangled_name = abi::__cxa_demangle(tname.c_str(), NULL, NULL, &status); if(status == 0) { tname = demangled_name; std::free(demangled_name); } return tname; }
quelle
free
inif
?nullptr
wenn der Status nicht 0 ist.Wie von Bunkar Typid (T) erwähnt, ist .name die Implementierung definiert.
Um dieses Problem zu vermeiden, können Sie die Boost.TypeIndex- Bibliothek verwenden.
Zum Beispiel:
boost::typeindex::type_id<T>().pretty_name() // human readable
quelle
boost
wieder für den Sieg. erstaunlich , was Boost funktioniert ohne Compiler - Unterstützung (auto
,regex
,foreach
,threads
,static_assert
, etc, etc ... Unterstützung vor Compiler / C ++ - Standard - Support).Die Antwort von Logan Capaldo ist richtig, kann aber geringfügig vereinfacht werden, da es nicht erforderlich ist, die Klasse jedes Mal zu spezialisieren. Man kann schreiben:
// in header template<typename T> struct TypeParseTraits { static const char* name; }; // in c-file #define REGISTER_PARSE_TYPE(X) \ template <> const char* TypeParseTraits<X>::name = #X REGISTER_PARSE_TYPE(int); REGISTER_PARSE_TYPE(double); REGISTER_PARSE_TYPE(FooClass); // etc...
Auf diese Weise können Sie auch die Anweisungen REGISTER_PARSE_TYPE in eine C ++ - Datei einfügen ...
quelle
Als Umformulierung von Andreys Antwort:
Die Boost TypeIndex- Bibliothek kann zum Drucken von Typnamen verwendet werden.
In einer Vorlage kann dies wie folgt lauten
#include <boost/type_index.hpp> #include <iostream> template<typename T> void printNameOfType() { std::cout << "Type of T: " << boost::typeindex::type_id<T>().pretty_name() << std::endl; }
quelle
Dieser Trick wurde unter einigen anderen Fragen erwähnt, aber noch nicht hier.
Alle wichtigen Compiler unterstützen
__PRETTY_FUNC__
(GCC & Clang) /__FUNCSIG__
(MSVC) als Erweiterung.Bei Verwendung in einer Vorlage wie dieser:
template <typename T> const char *foo() { #ifdef _MSC_VER return __FUNCSIG__; #else return __PRETTY_FUNCTION__; #endif }
Es erzeugt Strings in einem compilerabhängigen Format, die unter anderem den Namen von enthalten
T
.ZB
foo<float>()
kehrt zurück:"const char* foo() [with T = float]"
auf GCC"const char *foo() [T = float]"
auf Clang"const char *__cdecl foo<float>(void)"
auf MSVCSie können die Typnamen einfach aus diesen Zeichenfolgen analysieren. Sie müssen nur herausfinden, wie viele Junk-Zeichen Ihr Compiler vor und nach dem Typ einfügt.
Sie können dies sogar zur Kompilierungszeit vollständig tun.
Die resultierenden Namen können zwischen verschiedenen Compilern leicht variieren. Beispielsweise lässt GCC Standardvorlagenargumente aus und MSVC stellt Klassen das Wort voran
class
.Hier ist eine Implementierung, die ich verwendet habe. Alles wird zur Kompilierungszeit erledigt.
Anwendungsbeispiel:
std::cout << TypeName<float>() << '\n'; std::cout << TypeName(1.2f); << '\n';
Implementierung:
#include <array> #include <cstddef> namespace impl { template <typename T> constexpr const auto &RawTypeName() { #ifdef _MSC_VER return __FUNCSIG__; #else return __PRETTY_FUNCTION__; #endif } struct RawTypeNameFormat { std::size_t leading_junk = 0, trailing_junk = 0; }; // Returns `false` on failure. inline constexpr bool GetRawTypeNameFormat(RawTypeNameFormat *format) { const auto &str = RawTypeName<int>(); for (std::size_t i = 0;; i++) { if (str[i] == 'i' && str[i+1] == 'n' && str[i+2] == 't') { if (format) { format->leading_junk = i; format->trailing_junk = sizeof(str)-i-3-1; // `3` is the length of "int", `1` is the space for the null terminator. } return true; } } return false; } inline static constexpr RawTypeNameFormat format = []{ static_assert(GetRawTypeNameFormat(nullptr), "Unable to figure out how to generate type names on this compiler."); RawTypeNameFormat format; GetRawTypeNameFormat(&format); return format; }(); } // Returns the type name in a `std::array<char, N>` (null-terminated). template <typename T> [[nodiscard]] constexpr auto CexprTypeName() { constexpr std::size_t len = sizeof(impl::RawTypeName<T>()) - impl::format.leading_junk - impl::format.trailing_junk; std::array<char, len> name{}; for (std::size_t i = 0; i < len-1; i++) name[i] = impl::RawTypeName<T>()[i + impl::format.leading_junk]; return name; } template <typename T> [[nodiscard]] const char *TypeName() { static constexpr auto name = CexprTypeName<T>(); return name.data(); } template <typename T> [[nodiscard]] const char *TypeName(const T &) { return TypeName<T>(); }
quelle
Wenn Sie einen hübschen Namen möchten, kann die Lösung von Logan Capaldo nicht mit komplexen Datenstrukturen umgehen:
REGISTER_PARSE_TYPE(map<int,int>)
undtypeid(map<int,int>).name()
gibt mir ein Ergebnis vonSt3mapIiiSt4lessIiESaISt4pairIKiiEEE
Es gibt eine weitere interessante Antwort mit
unordered_map
odermap
von https://en.cppreference.com/w/cpp/types/type_index .#include <iostream> #include <unordered_map> #include <map> #include <typeindex> using namespace std; unordered_map<type_index,string> types_map_; int main(){ types_map_[typeid(int)]="int"; types_map_[typeid(float)]="float"; types_map_[typeid(map<int,int>)]="map<int,int>"; map<int,int> mp; cout<<types_map_[typeid(map<int,int>)]<<endl; cout<<types_map_[typeid(mp)]<<endl; return 0; }
quelle
typeid(uint8_t).name()
ist nett, aber es gibt "unsigned char" zurück, während Sie "uint8_t" erwarten können.Dieser Code gibt Ihnen den entsprechenden Typ zurück
#define DECLARE_SET_FORMAT_FOR(type) \ if ( typeid(type) == typeid(T) ) \ formatStr = #type; template<typename T> static std::string GetFormatName() { std::string formatStr; DECLARE_SET_FORMAT_FOR( uint8_t ) DECLARE_SET_FORMAT_FOR( int8_t ) DECLARE_SET_FORMAT_FOR( uint16_t ) DECLARE_SET_FORMAT_FOR( int16_t ) DECLARE_SET_FORMAT_FOR( uint32_t ) DECLARE_SET_FORMAT_FOR( int32_t ) DECLARE_SET_FORMAT_FOR( float ) // .. to be exptended with other standard types you want to be displayed smartly if ( formatStr.empty() ) { assert( false ); formatStr = typeid(T).name(); } return formatStr; }
quelle
return #type;
stattdessen?Ich lasse es einfach dort. Wenn jemand es noch braucht, können Sie dies verwenden:
template <class T> bool isString(T* t) { return false; } // normal case returns false template <> bool isString(char* t) { return true; } // but for char* or String.c_str() returns true . . .
Dies wird nur CHECK Typ nicht GET es und nur für 1 Typ oder 2.
quelle