Is het mogelijk om het type van een variabele in standaard C++ af te drukken?

Bijvoorbeeld:

int a = 12;
cout << typeof(a) << endl;

Verwachte uitvoer:

int

Antwoord 1, autoriteit 100%

C++11-update voor een heel oude vraag: Variabeletype afdrukken in C++.

Het geaccepteerde (en goede) antwoord is om typeid(a).name()te gebruiken, waarbij aeen variabelenaam is.

In C++11 hebben we nu decltype(x), waarmee een uitdrukking in een type kan worden veranderd. En decltype()heeft zijn eigen set zeer interessante regels. Bijvoorbeeld zullen decltype(a)en decltype((a))over het algemeen verschillende typen zijn (en om goede en begrijpelijke redenen als die redenen eenmaal bekend zijn).

Zal onze vertrouwde typeid(a).name()ons helpen deze dappere nieuwe wereld te verkennen?

Nee.

Maar de tool die dat wel doet, is niet zo ingewikkeld. En het is die tool die ik gebruik als antwoord op deze vraag. Ik zal deze nieuwe tool vergelijken en contrasteren met typeid(a).name(). En deze nieuwe tool is eigenlijk bovenop typeid(a).name()gebouwd.

Het fundamentele probleem:

typeid(a).name()

gooit cv-kwalificaties, referenties en lvalue/rvalue-ness weg. Bijvoorbeeld:

const int ci = 0;
std::cout << typeid(ci).name() << '\n';

Voor mij uitgangen:

i

en ik gok op MSVC-uitgangen:

int

D.w.z. de constis verdwenen. Dit is geen QOI-probleem (Quality Of Implementation). De standaard verplicht dit gedrag.

Wat ik hieronder aanbeveel is:

template <typename T> std::string type_name();

die als volgt zou worden gebruikt:

const int ci = 0;
std::cout << type_name<decltype(ci)>() << '\n';

en voor mij outputs:

int const

<disclaimer>Ik heb dit niet getest op MSVC. </disclaimer>Maar ik ben blij met feedback van degenen die dat wel doen.

De C++11-oplossing

Ik gebruik __cxa_demanglevoor niet-MSVC-platforms, zoals aanbevolen door ipapadopin zijn antwoord typen te ontrafelen. Maar op MSVC vertrouw ik typeidom namen te ontleden (niet getest). En deze kern is verpakt rond een aantal eenvoudige tests die cv-kwalificaties en verwijzingen naar het invoertype detecteren, herstellen en rapporteren.

#include <type_traits>
#include <typeinfo>
#ifndef _MSC_VER
#   include <cxxabi.h>
#endif
#include <memory>
#include <string>
#include <cstdlib>
template <class T>
std::string
type_name()
{
    typedef typename std::remove_reference<T>::type TR;
    std::unique_ptr<char, void(*)(void*)> own
           (
#ifndef _MSC_VER
                abi::__cxa_demangle(typeid(TR).name(), nullptr,
                                           nullptr, nullptr),
#else
                nullptr,
#endif
                std::free
           );
    std::string r = own != nullptr ? own.get() : typeid(TR).name();
    if (std::is_const<TR>::value)
        r += " const";
    if (std::is_volatile<TR>::value)
        r += " volatile";
    if (std::is_lvalue_reference<T>::value)
        r += "&";
    else if (std::is_rvalue_reference<T>::value)
        r += "&&";
    return r;
}

De resultaten

Met deze oplossing kan ik dit doen:

int& foo_lref();
int&& foo_rref();
int foo_value();
int
main()
{
    int i = 0;
    const int ci = 0;
    std::cout << "decltype(i) is " << type_name<decltype(i)>() << '\n';
    std::cout << "decltype((i)) is " << type_name<decltype((i))>() << '\n';
    std::cout << "decltype(ci) is " << type_name<decltype(ci)>() << '\n';
    std::cout << "decltype((ci)) is " << type_name<decltype((ci))>() << '\n';
    std::cout << "decltype(static_cast<int&>(i)) is " << type_name<decltype(static_cast<int&>(i))>() << '\n';
    std::cout << "decltype(static_cast<int&&>(i)) is " << type_name<decltype(static_cast<int&&>(i))>() << '\n';
    std::cout << "decltype(static_cast<int>(i)) is " << type_name<decltype(static_cast<int>(i))>() << '\n';
    std::cout << "decltype(foo_lref()) is " << type_name<decltype(foo_lref())>() << '\n';
    std::cout << "decltype(foo_rref()) is " << type_name<decltype(foo_rref())>() << '\n';
    std::cout << "decltype(foo_value()) is " << type_name<decltype(foo_value())>() << '\n';
}

en de uitvoer is:

decltype(i) is int
decltype((i)) is int&
decltype(ci) is int const
decltype((ci)) is int const&
decltype(static_cast<int&>(i)) is int&
decltype(static_cast<int&&>(i)) is int&&
decltype(static_cast<int>(i)) is int
decltype(foo_lref()) is int&
decltype(foo_rref()) is int&&
decltype(foo_value()) is int

Let op (bijvoorbeeld) het verschil tussen decltype(i)en decltype((i)). De eerste is het type van de verklaringvan i. De laatste is het “type” van de uitdrukkingi. (expressies hebben nooit een referentietype, maar als conventie vertegenwoordigt decltypelvalue-expressies met lvalu-referenties).

Dus deze tool is een uitstekend middel om alleen meer te leren over decltype, naast het verkennen en debuggen van je eigen code.

Als ik dit daarentegen zou bouwen op typeid(a).name(), zonder verloren cv-kwalificaties of referenties terug te voegen, zou de output zijn:

decltype(i) is int
decltype((i)) is int
decltype(ci) is int
decltype((ci)) is int
decltype(static_cast<int&>(i)) is int
decltype(static_cast<int&&>(i)) is int
decltype(static_cast<int>(i)) is int
decltype(foo_lref()) is int
decltype(foo_rref()) is int
decltype(foo_value()) is int

D.w.z. Elke referentie en cv-kwalificatie wordt verwijderd.

C++14-update

Net als je denkt dat je een oplossing voor een probleem hebt gevonden, komt er altijd iemand uit het niets die je een veel betere manier laat zien. 🙂

Dit antwoordvan Jamboreelaat zien hoe je de typenaam in C++14 kunt krijgen tijdens het compileren. Het is om een aantal redenen een briljante oplossing:

  1. Het is aan het compileren!
  2. Je laat de compiler zelf het werk doen in plaats van een bibliotheek (zelfs een std::lib). Dit betekent nauwkeurigere resultaten voor de nieuwste taalfuncties (zoals lambda’s).

Jamboree’santwoordlegt niet helemaal alles uit voor VS, en ik ben zijn code een beetje aan het aanpassen. Maar aangezien dit antwoord veel views krijgt, neem even de tijd om daarheen te gaan en zijn antwoord te upvoten, zonder welke deze update nooit zou zijn gebeurd.

#include <cstddef>
#include <stdexcept>
#include <cstring>
#include <ostream>
#ifndef _MSC_VER
#  if __cplusplus < 201103
#    define CONSTEXPR11_TN
#    define CONSTEXPR14_TN
#    define NOEXCEPT_TN
#  elif __cplusplus < 201402
#    define CONSTEXPR11_TN constexpr
#    define CONSTEXPR14_TN
#    define NOEXCEPT_TN noexcept
#  else
#    define CONSTEXPR11_TN constexpr
#    define CONSTEXPR14_TN constexpr
#    define NOEXCEPT_TN noexcept
#  endif
#else  // _MSC_VER
#  if _MSC_VER < 1900
#    define CONSTEXPR11_TN
#    define CONSTEXPR14_TN
#    define NOEXCEPT_TN
#  elif _MSC_VER < 2000
#    define CONSTEXPR11_TN constexpr
#    define CONSTEXPR14_TN
#    define NOEXCEPT_TN noexcept
#  else
#    define CONSTEXPR11_TN constexpr
#    define CONSTEXPR14_TN constexpr
#    define NOEXCEPT_TN noexcept
#  endif
#endif  // _MSC_VER
class static_string
{
    const char* const p_;
    const std::size_t sz_;
public:
    typedef const char* const_iterator;
    template <std::size_t N>
    CONSTEXPR11_TN static_string(const char(&a)[N]) NOEXCEPT_TN
        : p_(a)
        , sz_(N-1)
        {}
    CONSTEXPR11_TN static_string(const char* p, std::size_t N) NOEXCEPT_TN
        : p_(p)
        , sz_(N)
        {}
    CONSTEXPR11_TN const char* data() const NOEXCEPT_TN {return p_;}
    CONSTEXPR11_TN std::size_t size() const NOEXCEPT_TN {return sz_;}
    CONSTEXPR11_TN const_iterator begin() const NOEXCEPT_TN {return p_;}
    CONSTEXPR11_TN const_iterator end()   const NOEXCEPT_TN {return p_ + sz_;}
    CONSTEXPR11_TN char operator[](std::size_t n) const
    {
        return n < sz_ ? p_[n] : throw std::out_of_range("static_string");
    }
};
inline
std::ostream&
operator<<(std::ostream& os, static_string const& s)
{
    return os.write(s.data(), s.size());
}
template <class T>
CONSTEXPR14_TN
static_string
type_name()
{
#ifdef __clang__
    static_string p = __PRETTY_FUNCTION__;
    return static_string(p.data() + 31, p.size() - 31 - 1);
#elif defined(__GNUC__)
    static_string p = __PRETTY_FUNCTION__;
#  if __cplusplus < 201402
    return static_string(p.data() + 36, p.size() - 36 - 1);
#  else
    return static_string(p.data() + 46, p.size() - 46 - 1);
#  endif
#elif defined(_MSC_VER)
    static_string p = __FUNCSIG__;
    return static_string(p.data() + 38, p.size() - 38 - 7);
#endif
}

Deze code wordt automatisch teruggetrokken op de constexprals je nog steeds vastzit in het oude C++11. En als je met C++98/03 op de grotmuur schildert, wordt ook de noexceptopgeofferd.

C++17-update

In de onderstaande opmerkingen wijst Lybertaerop dat de nieuwe std::string_viewde static_string:

template <class T>
constexpr
std::string_view
type_name()
{
    using namespace std;
#ifdef __clang__
    string_view p = __PRETTY_FUNCTION__;
    return string_view(p.data() + 34, p.size() - 34 - 1);
#elif defined(__GNUC__)
    string_view p = __PRETTY_FUNCTION__;
#  if __cplusplus < 201402
    return string_view(p.data() + 36, p.size() - 36 - 1);
#  else
    return string_view(p.data() + 49, p.find(';', 49) - 49);
#  endif
#elif defined(_MSC_VER)
    string_view p = __FUNCSIG__;
    return string_view(p.data() + 84, p.size() - 84 - 7);
#endif
}

Ik heb de constanten voor VS bijgewerkt dankzij het zeer mooie detectivewerk van Jive Dadson in de reacties hieronder.

Bijwerken:

Bekijk deze herschrijving hieronderdie de onleesbare magische getallen in mijn nieuwste formulering elimineert.


Antwoord 2, autoriteit 39%

Probeer:

#include <typeinfo>
// …
std::cout << typeid(a).name() << '\n';

Mogelijk moet u RTTI activeren in uw compileropties om dit te laten werken. Bovendien is de uitvoer hiervan afhankelijk van de compiler. Het kan een onbewerkte typenaam zijn of een naamverminkend symbool of iets daartussenin.


Antwoord 3, autoriteit 17%

Zeer lelijk maar doet de truc als u alleen compileertijdinformatie wilt (bijvoorbeeld voor foutopsporing):

auto testVar = std::make_tuple(1, 1.0, "abc");
decltype(testVar)::foo= 1;

Retouren:

Compilation finished with errors:
source.cpp: In function 'int main()':
source.cpp:5:19: error: 'foo' is not a member of 'std::tuple<int, double, const char*>'

Antwoord 4, Autoriteit 12%

Volgens Howard oplossing, als u het magische nummer niet leuk vindt, denk ik dat dit een goed is manier om te vertegenwoordigen en het ziet er intuïtief uit:

#include <string_view>
template <typename T>
constexpr auto type_name() noexcept {
  std::string_view name = "Error: unsupported compiler", prefix, suffix;
#ifdef __clang__
  name = __PRETTY_FUNCTION__;
  prefix = "auto type_name() [T = ";
  suffix = "]";
#elif defined(__GNUC__)
  name = __PRETTY_FUNCTION__;
  prefix = "constexpr auto type_name() [with T = ";
  suffix = "]";
#elif defined(_MSC_VER)
  name = __FUNCSIG__;
  prefix = "auto __cdecl type_name<";
  suffix = ">(void) noexcept";
#endif
  name.remove_prefix(prefix.size());
  name.remove_suffix(suffix.size());
  return name;
}

demo.


Antwoord 5, Autoriteit 9%

Vergeet niet om <typeinfo>

Ik geloof waar je naar verwijst, is identificatie van runtime-type. U kunt het bovenstaande bereiken door te doen.

#include <iostream>
#include <typeinfo>
using namespace std;
int main() {
  int i;
  cout << typeid(i).name();
  return 0;
}

Antwoord 6, Autoriteit 4%

Merk op dat de namen gegenereerd door de RTTI-functie van C++ zijn niet draagbaar.
Bijvoorbeeld de klas

MyNamespace::CMyContainer<int, test_MyNamespace::CMyObject>

heeft de volgende namen:

// MSVC 2003:
class MyNamespace::CMyContainer[int,class test_MyNamespace::CMyObject]
// G++ 4.2:
N8MyNamespace8CMyContainerIiN13test_MyNamespace9CMyObjectEEE

Dus u kunt deze informatie niet gebruiken voor serialisatie. Maar toch kan het Type (A) -naam () eigenschap nog steeds worden gebruikt voor log / debug-doeleinden


Antwoord 7, Autoriteit 3%

U kunt sjablonen gebruiken.

template <typename T> const char* typeof(T&) { return "unknown"; }    // default
template<> const char* typeof(int&) { return "int"; }
template<> const char* typeof(float&) { return "float"; }

In het bovenstaande voorbeeld, wanneer het type niet is afgestemd, drukt het “onbekend” af.


Antwoord 8, Autoriteit 3%

Zoals vermeld, typeid().name()kan een verminkte naam retourneren. In GCC (en enkele andere compilers) kunt u er omheen werken met de volgende code:

#include <cxxabi.h>
#include <iostream>
#include <typeinfo>
#include <cstdlib>
namespace some_namespace { namespace another_namespace {
  class my_class { };
} }
int main() {
  typedef some_namespace::another_namespace::my_class my_type;
  // mangled
  std::cout << typeid(my_type).name() << std::endl;
  // unmangled
  int status = 0;
  char* demangled = abi::__cxa_demangle(typeid(my_type).name(), 0, 0, &status);
  switch (status) {
    case -1: {
      // could not allocate memory
      std::cout << "Could not allocate memory" << std::endl;
      return -1;
    } break;
    case -2: {
      // invalid name under the C++ ABI mangling rules
      std::cout << "Invalid name" << std::endl;
      return -1;
    } break;
    case -3: {
      // invalid argument
      std::cout << "Invalid argument to demangle()" << std::endl;
      return -1;
    } break;
 }
 std::cout << demangled << std::endl;
 free(demangled);
 return 0;

}


Antwoord 9, Autoriteit 2%

U kunt hiervoor een kenmerk van de kenmerken gebruiken. Zoiets als:

#include <iostream>
using namespace std;
template <typename T> class type_name {
public:
    static const char *name;
};
#define DECLARE_TYPE_NAME(x) template<> const char *type_name<x>::name = #x;
#define GET_TYPE_NAME(x) (type_name<typeof(x)>::name)
DECLARE_TYPE_NAME(int);
int main()
{
    int a = 12;
    cout << GET_TYPE_NAME(a) << endl;
}

De DECLARE_TYPE_NAMEDefinie bestaat om uw leven gemakkelijker te maken bij het declareren van deze kenmerken van deze kenmerken voor alle typen die u verwacht te hebben.

Dit kan nuttiger zijn dan de oplossingen met typeidomdat u de uitvoer kunt bedienen. Bijvoorbeeld, met behulp van typeidvoor long longop mijn compiler geeft “X”.


Antwoord 10, Autoriteit 2%

In C++11 hebben we decltype. Er is geen manier in standaard c++ om het exacte type variabele weer te geven dat is gedeclareerd met decltype. We kunnen boost typeindex gebruiken, d.w.z. type_id_with_cvr(cvr staat voor const, vluchtig, reference) om het type zoals hieronder af te drukken.

#include <iostream>
#include <boost/type_index.hpp>
using namespace std;
using boost::typeindex::type_id_with_cvr;
int main() {
  int i = 0;
  const int ci = 0;
  cout << "decltype(i) is " << type_id_with_cvr<decltype(i)>().pretty_name() << '\n';
  cout << "decltype((i)) is " << type_id_with_cvr<decltype((i))>().pretty_name() << '\n';
  cout << "decltype(ci) is " << type_id_with_cvr<decltype(ci)>().pretty_name() << '\n';
  cout << "decltype((ci)) is " << type_id_with_cvr<decltype((ci))>().pretty_name() << '\n';
  cout << "decltype(std::move(i)) is " << type_id_with_cvr<decltype(std::move(i))>().pretty_name() << '\n';
  cout << "decltype(std::static_cast<int&&>(i)) is " << type_id_with_cvr<decltype(static_cast<int&&>(i))>().pretty_name() << '\n';
  return 0;
}

Antwoord 11, autoriteit 2%

Howard Hinnantgebruikte magische getallen om de typenaam te extraheren. 康桓瑋voorgestelde tekenreeksvoorvoegsel en achtervoegsel. Maar het voorvoegsel/achtervoegsel blijft veranderen.
Met “probe_type” berekent type_name automatisch de grootte van voor- en achtervoegsels voor “probe_type” om de typenaam te extraheren:

#include <string_view>
using namespace std;
namespace typeName {
 template <typename T>
  constexpr string_view wrapped_type_name () {
#ifdef __clang__
    return __PRETTY_FUNCTION__;
#elif defined(__GNUC__)
    return  __PRETTY_FUNCTION__;
#elif defined(_MSC_VER)
    return  __FUNCSIG__;
#endif
  }
  class probe_type;
  constexpr string_view probe_type_name ("typeName::probe_type");
  constexpr string_view probe_type_name_elaborated ("class typeName::probe_type");
  constexpr string_view probe_type_name_used (wrapped_type_name<probe_type> ().find (probe_type_name_elaborated) != -1 ? probe_type_name_elaborated : probe_type_name);
  constexpr size_t prefix_size () {
    return wrapped_type_name<probe_type> ().find (probe_type_name_used);
  }
  constexpr size_t suffix_size () {
    return wrapped_type_name<probe_type> ().length () - prefix_size () - probe_type_name_used.length ();
  }
  template <typename T>
  string_view type_name () {
    constexpr auto type_name = wrapped_type_name<T> ();
    return type_name.substr (prefix_size (), type_name.length () - prefix_size () - suffix_size ());
  }
}
#include <iostream>
using typeName::type_name;
using typeName::probe_type;
class test;
int main () {
  cout << type_name<class test> () << endl;
  cout << type_name<const int*&> () << endl;
  cout << type_name<unsigned int> () << endl;
  const int ic = 42;
  const int* pic = &ic;
  const int*& rpic = pic;
  cout << type_name<decltype(ic)> () << endl;
  cout << type_name<decltype(pic)> () << endl;
  cout << type_name<decltype(rpic)> () << endl;
  cout << type_name<probe_type> () << endl;
}

Uitvoer

gcc 10.2:

test
const int *&
unsigned int
const int
const int *
const int *&
typeName::probe_type

clang 11.0.0:

test
const int *&
unsigned int
const int
const int *
const int *&
typeName::probe_type

VS 2019 versie 16.7.6:

class test
const int*&
unsigned int
const int
const int*
const int*&
class typeName::probe_type

Antwoord 12

Een andere kijk op @康桓瑋’s antwoord(oorspronkelijk), met minder aannames over de prefix- en suffix-specificaties, en geïnspireerd door @Val’s antwoord– maar zonder de globale naamruimte te vervuilen; zonder voorwaarden; en hopelijk gemakkelijker te lezen.

De populaire compilers leveren een macro met de handtekening van de huidige functie. Nu zijn functies sjabloonbaar; dus de handtekening bevat de sjabloonargumenten. De basisbenadering is dus: Gegeven een type, wees in een functie met dat type als sjabloonargument.

Helaas is de typenaam verpakt in tekst die de functie beschrijft, wat verschillend is tussen compilers. Met GCC kan bijvoorbeeld de handtekening van template <typename T> int foo()met type doubleis: int foo() [T = double].

Dus, hoe komt u de wrapper-tekst af? @ Howardhinnant’s oplossing is de kortste en meest “directe”: gebruik gewoon per-compiler magische nummers om een ​​voorvoegsel en een achtervoegsel te verwijderen. Maar dat is duidelijk, dat is erg broos; en niemand houdt van magische nummers in hun code. In plaats daarvan krijgt u de macro-waarde voor een type met een bekende naam, u kunt bepalen welk voorvoegsel en achtervoegsel de verpakking vormen.

#include <string_view>
template <typename T> constexpr std::string_view type_name();
template <>
constexpr std::string_view type_name<void>()
{ return "void"; }
namespace detail {
using type_name_prober = void;
template <typename T>
constexpr std::string_view wrapped_type_name() 
{
#ifdef __clang__
    return __PRETTY_FUNCTION__;
#elif defined(__GNUC__)
    return __PRETTY_FUNCTION__;
#elif defined(_MSC_VER)
    return __FUNCSIG__;
#else
#error "Unsupported compiler"
#endif
}
constexpr std::size_t wrapped_type_name_prefix_length() { 
    return wrapped_type_name<type_name_prober>().find(type_name<type_name_prober>()); 
}
constexpr std::size_t wrapped_type_name_suffix_length() { 
    return wrapped_type_name<type_name_prober>().length() 
        - wrapped_type_name_prefix_length() 
        - type_name<type_name_prober>().length();
}
} // namespace detail
template <typename T>
constexpr std::string_view type_name() {
    constexpr auto wrapped_name = detail::wrapped_type_name<T>();
    constexpr auto prefix_length = detail::wrapped_type_name_prefix_length();
    constexpr auto suffix_length = detail::wrapped_type_name_suffix_length();
    constexpr auto type_name_length = wrapped_name.length() - prefix_length - suffix_length;
    return wrapped_name.substr(prefix_length, type_name_length);
}

Bekijk het op GodBolt. Dit zou ook moeten werken met MSVC.


Antwoord 13

Je kunt ook c++filt gebruiken met optie -t (type) om de typenaam te ontrafelen:

#include <iostream>
#include <typeinfo>
#include <string>
using namespace std;
int main() {
  auto x = 1;
  string my_type = typeid(x).name();
  system(("echo " + my_type + " | c++filt -t").c_str());
  return 0;
}

Alleen getest op Linux.


Antwoord 14

De andere antwoorden met betrekking tot RTTI (typeid) zijn waarschijnlijk wat u wilt, zolang als:

  • je kunt de geheugenoverhead betalen (wat bij sommige compilers aanzienlijk kan zijn)
  • de klassenamen die je compiler retourneert zijn handig

Het alternatief (vergelijkbaar met het antwoord van Greg Hewgill) is om een compilatietabel met eigenschappen te maken.

template <typename T> struct type_as_string;
// declare your Wibble type (probably with definition of Wibble)
template <>
struct type_as_string<Wibble>
{
    static const char* const value = "Wibble";
};

Houd er rekening mee dat als u de declaraties in een macro plaatst, u vanwege de komma problemen zult hebben met het declareren van namen voor sjabloontypen die meer dan één parameter bevatten (bijv. std::map).

Om toegang te krijgen tot de naam van het type variabele, is alles wat je nodig hebt

template <typename T>
const char* get_type_as_string(const T&)
{
    return type_as_string<T>::value;
}

Antwoord 15

Een meer generieke oplossing zonder overbelasting van functies dan mijn vorige:

template<typename T>
std::string TypeOf(T){
    std::string Type="unknown";
    if(std::is_same<T,int>::value) Type="int";
    if(std::is_same<T,std::string>::value) Type="String";
    if(std::is_same<T,MyClass>::value) Type="MyClass";
    return Type;}

Hier is MyClass de door de gebruiker gedefinieerde klasse. Hier kunnen ook meer voorwaarden worden toegevoegd.

Voorbeeld:

#include <iostream>
class MyClass{};
template<typename T>
std::string TypeOf(T){
    std::string Type="unknown";
    if(std::is_same<T,int>::value) Type="int";
    if(std::is_same<T,std::string>::value) Type="String";
    if(std::is_same<T,MyClass>::value) Type="MyClass";
    return Type;}
int main(){;
    int a=0;
    std::string s="";
    MyClass my;
    std::cout<<TypeOf(a)<<std::endl;
    std::cout<<TypeOf(s)<<std::endl;
    std::cout<<TypeOf(my)<<std::endl;
    return 0;}

Uitvoer:

int
String
MyClass

Antwoord 16

Ik hou van Nick’s methode. Een volledig formulier kan dit zijn (voor alle basisgegevenstypen):

template <typename T> const char* typeof(T&) { return "unknown"; }    // default
template<> const char* typeof(int&) { return "int"; }
template<> const char* typeof(short&) { return "short"; }
template<> const char* typeof(long&) { return "long"; }
template<> const char* typeof(unsigned&) { return "unsigned"; }
template<> const char* typeof(unsigned short&) { return "unsigned short"; }
template<> const char* typeof(unsigned long&) { return "unsigned long"; }
template<> const char* typeof(float&) { return "float"; }
template<> const char* typeof(double&) { return "double"; }
template<> const char* typeof(long double&) { return "long double"; }
template<> const char* typeof(std::string&) { return "String"; }
template<> const char* typeof(char&) { return "char"; }
template<> const char* typeof(signed char&) { return "signed char"; }
template<> const char* typeof(unsigned char&) { return "unsigned char"; }
template<> const char* typeof(char*&) { return "char*"; }
template<> const char* typeof(signed char*&) { return "signed char*"; }
template<> const char* typeof(unsigned char*&) { return "unsigned char*"; }

Antwoord 17

Zoals ik uitdaag, besloot ik om te testen hoe ver kan één gaan met platformonafhankelijke (hopelijk) sjabloonstrunheid.

De namen worden volledig geassembleerd bij compilatietijd. (Wat betekent typeid(T).name()KON NIET WORDEN GEBRUIKT, dus u moet expliciet namen voor niet-samengestelde typen verstrekken. Anders worden tijdelijke aanduidingen in plaats daarvan weergegeven.)

Voorbeeld Gebruik:

TYPE_NAME(int)
TYPE_NAME(void)
// You probably should list all primitive types here.
TYPE_NAME(std::string)
int main()
{
    // A simple case
    std::cout << type_name<void(*)(int)> << '\n';
    // -> `void (*)(int)`
    // Ugly mess case
    // Note that compiler removes cv-qualifiers from parameters and replaces arrays with pointers.
    std::cout << type_name<void (std::string::*(int[3],const int, void (*)(std::string)))(volatile int*const*)> << '\n';
    // -> `void (std::string::*(int *,int,void (*)(std::string)))(volatile int *const*)`
    // A case with undefined types
    //  If a type wasn't TYPE_NAME'd, it's replaced by a placeholder, one of `class?`, `union?`, `enum?` or `??`.
    std::cout << type_name<std::ostream (*)(int, short)> << '\n';
    // -> `class? (*)(int,??)`
    // With appropriate TYPE_NAME's, the output would be `std::string (*)(int,short)`.
}

Code:

#include <type_traits>
#include <utility>
static constexpr std::size_t max_str_lit_len = 256;
template <std::size_t I, std::size_t N> constexpr char sl_at(const char (&str)[N])
{
    if constexpr(I < N)
        return str[I];
    else
        return '\0';
}
constexpr std::size_t sl_len(const char *str)
{
    for (std::size_t i = 0; i < max_str_lit_len; i++)
        if (str[i] == '\0')
            return i;
    return 0;
}
template <char ...C> struct str_lit
{
    static constexpr char value[] {C..., '\0'};
    static constexpr int size = sl_len(value);
    template <typename F, typename ...P> struct concat_impl {using type = typename concat_impl<F>::type::template concat_impl<P...>::type;};
    template <char ...CC> struct concat_impl<str_lit<CC...>> {using type = str_lit<C..., CC...>;};
    template <typename ...P> using concat = typename concat_impl<P...>::type;
};
template <typename, const char *> struct trim_str_lit_impl;
template <std::size_t ...I, const char *S> struct trim_str_lit_impl<std::index_sequence<I...>, S>
{
    using type = str_lit<S[I]...>;
};
template <std::size_t N, const char *S> using trim_str_lit = typename trim_str_lit_impl<std::make_index_sequence<N>, S>::type;
#define STR_LIT(str) ::trim_str_lit<::sl_len(str), ::str_lit<STR_TO_VA(str)>::value>
#define STR_TO_VA(str) STR_TO_VA_16(str,0),STR_TO_VA_16(str,16),STR_TO_VA_16(str,32),STR_TO_VA_16(str,48)
#define STR_TO_VA_16(str,off) STR_TO_VA_4(str,0+off),STR_TO_VA_4(str,4+off),STR_TO_VA_4(str,8+off),STR_TO_VA_4(str,12+off)
#define STR_TO_VA_4(str,off) ::sl_at<off+0>(str),::sl_at<off+1>(str),::sl_at<off+2>(str),::sl_at<off+3>(str)
template <char ...C> constexpr str_lit<C...> make_str_lit(str_lit<C...>) {return {};}
template <std::size_t N> constexpr auto make_str_lit(const char (&str)[N])
{
    return trim_str_lit<sl_len((const char (&)[N])str), str>{};
}
template <std::size_t A, std::size_t B> struct cexpr_pow {static constexpr std::size_t value = A * cexpr_pow<A,B-1>::value;};
template <std::size_t A> struct cexpr_pow<A,0> {static constexpr std::size_t value = 1;};
template <std::size_t N, std::size_t X, typename = std::make_index_sequence<X>> struct num_to_str_lit_impl;
template <std::size_t N, std::size_t X, std::size_t ...Seq> struct num_to_str_lit_impl<N, X, std::index_sequence<Seq...>>
{
    static constexpr auto func()
    {
        if constexpr (N >= cexpr_pow<10,X>::value)
            return num_to_str_lit_impl<N, X+1>::func();
        else
            return str_lit<(N / cexpr_pow<10,X-1-Seq>::value % 10 + '0')...>{};
    }
};
template <std::size_t N> using num_to_str_lit = decltype(num_to_str_lit_impl<N,1>::func());
using spa = str_lit<' '>;
using lpa = str_lit<'('>;
using rpa = str_lit<')'>;
using lbr = str_lit<'['>;
using rbr = str_lit<']'>;
using ast = str_lit<'*'>;
using amp = str_lit<'&'>;
using con = str_lit<'c','o','n','s','t'>;
using vol = str_lit<'v','o','l','a','t','i','l','e'>;
using con_vol = con::concat<spa, vol>;
using nsp = str_lit<':',':'>;
using com = str_lit<','>;
using unk = str_lit<'?','?'>;
using c_cla = str_lit<'c','l','a','s','s','?'>;
using c_uni = str_lit<'u','n','i','o','n','?'>;
using c_enu = str_lit<'e','n','u','m','?'>;
template <typename T> inline constexpr bool ptr_or_ref = std::is_pointer_v<T> || std::is_reference_v<T> || std::is_member_pointer_v<T>;
template <typename T> inline constexpr bool func_or_arr = std::is_function_v<T> || std::is_array_v<T>;
template <typename T> struct primitive_type_name {using value = unk;};
template <typename T, typename = std::enable_if_t<std::is_class_v<T>>> using enable_if_class = T;
template <typename T, typename = std::enable_if_t<std::is_union_v<T>>> using enable_if_union = T;
template <typename T, typename = std::enable_if_t<std::is_enum_v <T>>> using enable_if_enum  = T;
template <typename T> struct primitive_type_name<enable_if_class<T>> {using value = c_cla;};
template <typename T> struct primitive_type_name<enable_if_union<T>> {using value = c_uni;};
template <typename T> struct primitive_type_name<enable_if_enum <T>> {using value = c_enu;};
template <typename T> struct type_name_impl;
template <typename T> using type_name_lit = std::conditional_t<std::is_same_v<typename primitive_type_name<T>::value::template concat<spa>,
                                                                               typename type_name_impl<T>::l::template concat<typename type_name_impl<T>::r>>,
                                            typename primitive_type_name<T>::value,
                                            typename type_name_impl<T>::l::template concat<typename type_name_impl<T>::r>>;
template <typename T> inline constexpr const char *type_name = type_name_lit<T>::value;
template <typename T, typename = std::enable_if_t<!std::is_const_v<T> && !std::is_volatile_v<T>>> using enable_if_no_cv = T;
template <typename T> struct type_name_impl
{
    using l = typename primitive_type_name<T>::value::template concat<spa>;
    using r = str_lit<>;
};
template <typename T> struct type_name_impl<const T>
{
    using new_T_l = std::conditional_t<type_name_impl<T>::l::size && !ptr_or_ref<T>,
                                       spa::concat<typename type_name_impl<T>::l>,
                                       typename type_name_impl<T>::l>;
    using l = std::conditional_t<ptr_or_ref<T>,
                                 typename new_T_l::template concat<con>,
                                 con::concat<new_T_l>>;
    using r = typename type_name_impl<T>::r;
};
template <typename T> struct type_name_impl<volatile T>
{
    using new_T_l = std::conditional_t<type_name_impl<T>::l::size && !ptr_or_ref<T>,
                                       spa::concat<typename type_name_impl<T>::l>,
                                       typename type_name_impl<T>::l>;
    using l = std::conditional_t<ptr_or_ref<T>,
                                 typename new_T_l::template concat<vol>,
                                 vol::concat<new_T_l>>;
    using r = typename type_name_impl<T>::r;
};
template <typename T> struct type_name_impl<const volatile T>
{
    using new_T_l = std::conditional_t<type_name_impl<T>::l::size && !ptr_or_ref<T>,
                                       spa::concat<typename type_name_impl<T>::l>,
                                       typename type_name_impl<T>::l>;
    using l = std::conditional_t<ptr_or_ref<T>,
                                 typename new_T_l::template concat<con_vol>,
                                 con_vol::concat<new_T_l>>;
    using r = typename type_name_impl<T>::r;
};
template <typename T> struct type_name_impl<T *>
{
    using l = std::conditional_t<func_or_arr<T>,
                                 typename type_name_impl<T>::l::template concat<lpa, ast>,
                                 typename type_name_impl<T>::l::template concat<     ast>>;
    using r = std::conditional_t<func_or_arr<T>,
                                 rpa::concat<typename type_name_impl<T>::r>,
                                             typename type_name_impl<T>::r>;
};
template <typename T> struct type_name_impl<T &>
{
    using l = std::conditional_t<func_or_arr<T>,
                                 typename type_name_impl<T>::l::template concat<lpa, amp>,
                                 typename type_name_impl<T>::l::template concat<     amp>>;
    using r = std::conditional_t<func_or_arr<T>,
                                 rpa::concat<typename type_name_impl<T>::r>,
                                             typename type_name_impl<T>::r>;
};
template <typename T> struct type_name_impl<T &&>
{
    using l = std::conditional_t<func_or_arr<T>,
                                 typename type_name_impl<T>::l::template concat<lpa, amp, amp>,
                                 typename type_name_impl<T>::l::template concat<     amp, amp>>;
    using r = std::conditional_t<func_or_arr<T>,
                                 rpa::concat<typename type_name_impl<T>::r>,
                                             typename type_name_impl<T>::r>;
};
template <typename T, typename C> struct type_name_impl<T C::*>
{
    using l = std::conditional_t<func_or_arr<T>,
                                 typename type_name_impl<T>::l::template concat<lpa, type_name_lit<C>, nsp, ast>,
                                 typename type_name_impl<T>::l::template concat<     type_name_lit<C>, nsp, ast>>;
    using r = std::conditional_t<func_or_arr<T>,
                                 rpa::concat<typename type_name_impl<T>::r>,
                                             typename type_name_impl<T>::r>;
};
template <typename T> struct type_name_impl<enable_if_no_cv<T[]>>
{
    using l = typename type_name_impl<T>::l;
    using r = lbr::concat<rbr, typename type_name_impl<T>::r>;
};
template <typename T, std::size_t N> struct type_name_impl<enable_if_no_cv<T[N]>>
{
    using l = typename type_name_impl<T>::l;
    using r = lbr::concat<num_to_str_lit<N>, rbr, typename type_name_impl<T>::r>;
};
template <typename T> struct type_name_impl<T()>
{
    using l = typename type_name_impl<T>::l;
    using r = lpa::concat<rpa, typename type_name_impl<T>::r>;
};
template <typename T, typename P1, typename ...P> struct type_name_impl<T(P1, P...)>
{
    using l = typename type_name_impl<T>::l;
    using r = lpa::concat<type_name_lit<P1>,
                          com::concat<type_name_lit<P>>..., rpa, typename type_name_impl<T>::r>;
};
#define TYPE_NAME(t) template <> struct primitive_type_name<t> {using value = STR_LIT(#t);};

Antwoord 18

#include <iostream>
#include <typeinfo>
using namespace std;
#define show_type_name(_t) \
    system(("echo " + string(typeid(_t).name()) + " | c++filt -t").c_str())
int main() {
    auto a = {"one", "two", "three"};
    cout << "Type of a: " << typeid(a).name() << endl;
    cout << "Real type of a:\n";
    show_type_name(a);
    for (auto s : a) {
        if (string(s) == "one") {
            cout << "Type of s: " << typeid(s).name() << endl;
            cout << "Real type of s:\n";
            show_type_name(s);
        }
        cout << s << endl;
    }
    int i = 5;
    cout << "Type of i: " << typeid(i).name() << endl;
    cout << "Real type of i:\n";
    show_type_name(i);
    return 0;
}

Uitgang:

Type of a: St16initializer_listIPKcE
Real type of a:
std::initializer_list<char const*>
Type of s: PKc
Real type of s:
char const*
one
two
three
Type of i: i
Real type of i:
int

Antwoord 19

Zoals uitgelegd door Scott Meyers in effectieve moderne C++,

Oproepen naar std::type_info::nameKUNNEN GEGEVEN GEGEVEN GEGEVEN OM ALTIJD DIENT SIFTIBLE TE KOMEN.

De beste oplossing is om de compiler te laten genereren tijdens het type aftrek, bijvoorbeeld

template<typename T>
class TD;
int main(){
    const int theAnswer = 32;
    auto x = theAnswer;
    auto y = &theAnswer;
    TD<decltype(x)> xType;
    TD<decltype(y)> yType;
    return 0;
}

Het resultaat is zoiets, afhankelijk van de compilers,

test4.cpp:10:21: error: aggregate ‘TD<int> xType’ has incomplete type and cannot be defined TD<decltype(x)> xType;
test4.cpp:11:21: error: aggregate ‘TD<const int *> yType’ has incomplete type and cannot be defined TD<decltype(y)> yType;

Vandaar dat we leren kennen dat x‘s typen int, y‘s type bevindt is const int*


Antwoord 20

Voor iedereen die nog steeds bezoekt, heb ik onlangs hetzelfde probleem gehad en besloten om een ​​kleine bibliotheek te schrijven op basis van antwoorden van dit bericht. Het biedt de naam van het Constexpr Type en Type-indices und is getest op Mac, Windows en Ubuntu.

De bibliotheekcode is hier: https://github.com/thelartians/staticypeinfo


Antwoord 21

Kopiëren van dit antwoord: https://stackoverflow.com/a/56766138/11502722

Ik heb dit enigszinswerkend kunnen krijgen voor C++ static_assert(). De rimpel hier is dat static_assert()alleen letterlijke tekenreeksen accepteert; constexpr string_viewzal niet werken. Je moet extra tekst rond de typenaam accepteren, maar het werkt:

template<typename T>
constexpr void assertIfTestFailed()
{
#ifdef __clang__
    static_assert(testFn<T>(), "Test failed on this used type: " __PRETTY_FUNCTION__);
#elif defined(__GNUC__)
    static_assert(testFn<T>(), "Test failed on this used type: " __PRETTY_FUNCTION__);
#elif defined(_MSC_VER)
    static_assert(testFn<T>(), "Test failed on this used type: " __FUNCSIG__);
#else
    static_assert(testFn<T>(), "Test failed on this used type (see surrounding logged error for details).");
#endif
    }
}

MSVC-uitvoer:

error C2338: Test failed on this used type: void __cdecl assertIfTestFailed<class BadType>(void)
... continued trace of where the erroring code came from ...

Antwoord 22

Voor iets anders, hier is een ‘naar het Engels’-conversie van het type, waarbij elke kwalificatie, omvang, argument, enzovoort wordt gedeconstrueerd, en recursief de tekenreeks wordt opgebouwd die het type beschrijft. Ik denk dat het voorstel ‘deduced this’ zou helpen veel te verminderen van de specialisaties. In ieder geval was dit een leuke ochtendoefening, ongeacht een overmatig opgeblazen gevoel. 🙂

struct X {
    using T = int *((*)[10]);
    T f(T, const unsigned long long * volatile * );
};
int main() {
    std::cout << describe<decltype(&X::f)>() << std::endl;
}

Uitvoer:

pointer to member function of class 1X taking (pointer to array[10]
of pointer to int, pointer to volatile pointer to const unsigned 
long long), and returning pointer to array[10] of pointer to int

Hier is de code:
https://godbolt.org/z/7jKK4or43

// Print types as strings, including functions, member 
#include <type_traits>
#include <typeinfo>
#include <string>
#include <utility>
namespace detail {
template <typename T> struct Describe;
template <typename T, class ClassT> 
struct Describe<T (ClassT::*)>  {
    static std::string describe();
};
template <typename RetT, typename... ArgsT> 
struct Describe<RetT(ArgsT...)>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...)>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) volatile>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const volatile>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) volatile noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const volatile noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...)&>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const &>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) volatile &>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) & noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const volatile &>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const & noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) volatile & noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const volatile & noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) &&>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const &&>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) volatile &&>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) && noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const volatile &&>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const && noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) volatile && noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const volatile && noexcept>  {
    static std::string describe();
};
template <typename T>
std::string describe()
{
    using namespace std::string_literals;
    auto terminal = [&](char const * desc) {
        return desc + " "s + typeid(T).name();
    };
    if constexpr(std::is_const_v<T>) {
        return "const " + describe<std::remove_const_t<T>>();
    }
    else if constexpr(std::is_volatile_v<T>) {
        return "volatile " + describe<std::remove_volatile_t<T>>();
    }
    else if constexpr (std::is_same_v<bool, T>) {
        return "bool";
    }
    else if constexpr(std::is_same_v<char, T>) {
        return "char";
    }
    else if constexpr(std::is_same_v<signed char, T>) {
        return "signed char";
    }
    else if constexpr(std::is_same_v<unsigned char, T>) {
        return "unsigned char";
    }
    else if constexpr(std::is_unsigned_v<T>) {
        return "unsigned " + describe<std::make_signed_t<T>>();
    }
    else if constexpr(std::is_void_v<T>) {
        return "void";
    }
    else if constexpr(std::is_integral_v<T>) {
        if constexpr(std::is_same_v<short, T>) 
            return "short";
        else if constexpr(std::is_same_v<int, T>) 
            return "int";
        else if constexpr(std::is_same_v<long, T>) 
            return "long";
        else if constexpr(std::is_same_v<long long, T>) 
            return "long long";
    }
    else if constexpr(std::is_same_v<float, T>) {
        return "float";
    }
    else if constexpr(std::is_same_v<double, T>) {
        return "double";
    }
    else if constexpr(std::is_same_v<long double, T>) {
        return "long double";
    }
    else if constexpr(std::is_same_v<std::nullptr_t, T>) { 
        return "nullptr_t";
    }
    else if constexpr(std::is_class_v<T>) {
        return terminal("class");
    }
    else if constexpr(std::is_union_v<T>) {
        return terminal("union");
    }
    else if constexpr(std::is_enum_v<T>) {
        std::string result;
        if (!std::is_convertible_v<T, std::underlying_type_t<T>>) {
            result += "scoped ";
        }
        return result + terminal("enum");
    }  
    else if constexpr(std::is_pointer_v<T>) {
        return "pointer to " + describe<std::remove_pointer_t<T>>();
    }
    else if constexpr(std::is_lvalue_reference_v<T>) {
        return "lvalue-ref to " + describe<std::remove_reference_t<T>>();
    }
    else if constexpr(std::is_rvalue_reference_v<T>) {
        return "rvalue-ref to " + describe<std::remove_reference_t<T>>();
    }
    else if constexpr(std::is_bounded_array_v<T>) {
        return "array[" + std::to_string(std::extent_v<T>) + "] of " +
            describe<std::remove_extent_t<T>>();
    }
    else if constexpr(std::is_unbounded_array_v<T>) {
        return "array[] of " + describe<std::remove_extent_t<T>>();
    }
    else if constexpr(std::is_function_v<T>) {
        return Describe<T>::describe();
    }
    else if constexpr(std::is_member_object_pointer_v<T>) {
        return Describe<T>::describe();
    }
    else if constexpr(std::is_member_function_pointer_v<T>) {
        return Describe<T>::describe();
    }
}
template <typename RetT, typename... ArgsT> 
std::string Describe<RetT(ArgsT...)>::describe() {
    std::string result = "function taking (";
    ((result += detail::describe<ArgsT>(", ")), ...);
    return result + "), returning " + detail::describe<RetT>();
}
template <typename T, class ClassT> 
std::string Describe<T (ClassT::*)>::describe() {
    return "pointer to member of " + detail::describe<ClassT>() +
        " of type " + detail::describe<T>();
}
struct Comma {
    char const * sep = "";
    std::string operator()(std::string const& str) {
        return std::exchange(sep, ", ") + str;
    }
};
enum Qualifiers {NONE=0, CONST=1, VOLATILE=2, NOEXCEPT=4, LVREF=8, RVREF=16};
template <typename RetT, typename ClassT, typename... ArgsT>
std::string describeMemberPointer(Qualifiers q) {
    std::string result = "pointer to ";
    if (NONE != (q & CONST)) result += "const ";
    if (NONE != (q & VOLATILE)) result += "volatile ";
    if (NONE != (q & NOEXCEPT)) result += "noexcept ";
    if (NONE != (q & LVREF)) result += "lvalue-ref ";
    if (NONE != (q & RVREF)) result += "rvalue-ref ";
    result += "member function of " + detail::describe<ClassT>() + " taking (";
    Comma comma;
    ((result += comma(detail::describe<ArgsT>())), ...);
    return result + "), and returning " + detail::describe<RetT>();
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...)>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(NONE);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(CONST);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(NOEXCEPT);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) volatile>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(VOLATILE);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) volatile noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(VOLATILE | NOEXCEPT);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const volatile>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(CONST | VOLATILE);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(CONST | NOEXCEPT);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const volatile noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(CONST | VOLATILE | NOEXCEPT);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) &>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(LVREF);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const &>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(LVREF | CONST);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) & noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(LVREF | NOEXCEPT);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) volatile &>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(LVREF | VOLATILE);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) volatile & noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(LVREF | VOLATILE | NOEXCEPT);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const volatile &>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(LVREF | CONST | VOLATILE);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const & noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(LVREF | CONST | NOEXCEPT);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const volatile & noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(LVREF | CONST | VOLATILE | NOEXCEPT);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...)&&>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(RVREF);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const &&>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(RVREF | CONST);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) && noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(RVREF | NOEXCEPT);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) volatile &&>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(RVREF | VOLATILE);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) volatile && noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(RVREF | VOLATILE | NOEXCEPT);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const volatile &&>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(RVREF | CONST | VOLATILE);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const && noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(RVREF | CONST | NOEXCEPT);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const volatile && noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(RVREF | CONST | VOLATILE | NOEXCEPT);
}
} // detail
///////////////////////////////////
// Main function
///////////////////////////////////
template <typename T>
std::string describe() {
    return detail::describe<T>();
}
///////////////////////////////////
// Sample code
///////////////////////////////////
#include <iostream>
struct X {
    using T = int *((*)[10]);
    T f(T, const unsigned long long * volatile * );
};
int main() {
    std::cout << describe<decltype(&X::f)>() << std::endl;
}

Other episodes