ambiguous template specialization in variadic function template - c++

I am trying to write a variadic function template to calculate the byte size of a struct. This is going to be used in a network programming project I am working on. The first step I came up with this without the variadic template that works:
#include <cstdint>
#include <iostream>
struct Position
{
int32_t x;
int32_t y;
};
template<typename T>
inline std::size_t sizeT() { return sizeof(T); }
template<>
inline std::size_t sizeT<Position>() { return sizeT<int32_t>() + sizeT<int32_t>(); }
// if I change the definition of Position, I need to remember to change this function
int main(int argc, char* argv[])
{
std::cout << "int8_t:" << sizeT<int8_t>() << std::endl;
std::cout << "Position:" << sizeT<Position>() << std::endl;
return 0;
}
I compiled with g++ 9.3.0 on my Ubuntu 20.04 laptop and it worked fine. However if I try to make it a variadic template then I ran into different compilation errors. The point of this variadic template is to be able to write like the following, so that the implementation doesn't depend on the explicit knowledge of x and y's types.
Here is bad code #1
#include <cstdint>
#include <iostream>
struct Position
{
int32_t x;
int32_t y;
};
template<typename T>
inline std::size_t sizeT() { return sizeof(T); }
template<>
inline std::size_t sizeT<Position>()
{
return sizeT<decltype(Position::x), decltype(Position::y)>();
}
template<>
inline std::size_t sizeT<Position>() { return sizeT<int32_t>() + sizeT<int32_t>(); }
int main(int argc, char* argv[])
{
std::cout << "int8_t:" << sizeT<int8_t>() << std::endl;
std::cout << "Position:" << sizeT<Position>() << std::endl;
return 0;
}
I got
error: ambiguous template specialization 'sizeT' for 'std::size_t sizeT()
note: candidate are: 'template std::size_t sizeT()'
note: template<class T, class... Ts> std::size_t sizeT()'
Bad code #2. If I put the variadic template at a different location:
#include <cstdint>
#include <iostream>
struct Position
{
int32_t x;
int32_t y;
};
template<typename T>
inline std::size_t sizeT() { return sizeof(T); }
template<>
inline std::size_t sizeT<Position>() { return sizeT<int32_t>() + sizeT<int32_t>(); }
template<>
inline std::size_t sizeT<Position>()
{
return sizeT<decltype(Position::x), decltype(Position::y)>();
}
int main(int argc, char* argv[])
{
std::cout << "int8_t:" << sizeT<int8_t>() << std::endl;
std::cout << "Position:" << sizeT<Position>() << std::endl;
return 0;
}
I got
error: call of overloaded 'sizeT<int8_t>()' is ambiguous
note candidate: 'std::size_t sizeT() [with T=signed char; std::size_t = long unsigned int]'
note candidate: 'std::size_t sizeT() [with T=signed char; Ts={}; std::size_t = long unsigned int]'
error: call of overloaded 'sizeT()' is ambiguous
note candidate: 'std::size_t sizeT() [with T=Position; std::size_t = long unsigned int]'
note candidate: 'std::size_t sizeT() [with T=Position; Ts={}; std::size_t = long unsigned int]'
I kinda understand case #2 but I don't understand #1. How can I achieve this? Thank you so much in advance.

You don't use variadic...
You might do (C++17)
template <typename ... Ts>
constexpr std::size_t sizeT() { return (0 + ... + sizeof(Ts)); }
template <>
constexpr std::size_t sizeT<Position>()
{
return sizeT<decltype(Position::x), decltype(Position::y)>();
}
Demo
But sizeT<Position, Position> would return 2 * sizeof(Position) instead of 2 * sizeT<Position> (which are identical here).
You might do instead:
template <typename... Ts>
struct tag{};
template <typename T>
constexpr std::size_t sizeT(tag<T>) { return sizeof(T); }
template <typename ... Ts>
constexpr std::size_t sizeT(tag<Ts...>) { return (0 + ... + sizeT(tag<Ts>())); }
template <typename ... Ts>
constexpr std::size_t sizeT(tag<Ts>...) { return (0 + ... + sizeT(tag<Ts>())); }
constexpr std::size_t sizeT(tag<Position>)
{
return sizeT(tag<decltype(Position::x), decltype(Position::y)>());
}
Demo

Related

How to access first parameter in parameter pack?

Is it possible to statically "unroll" a parameter list at compile time, giving using one parameter in every "unroll" step? I think variadic templates are the way to go combined with partial template specialization, but I cannot get this example to run:
#include <iostream>
char static const text1[] = "Foo";
char static const text2[] = "FooBar";
template <char const * TEXT, unsigned int N, char const *... REST, unsigned int... Ns>
void doStuff() {
std :: cout << TEXT << "-" << N << std :: endl;
doStuff<REST..., Ns...>();
}
template <char const * TEXT, unsigned int N>
void doStuff() {
std :: cout << TEXT << std :: endl;
}
void doStuff() {}
int main() {
doStuff<text1,3,text2,5>();
return 0;
}
My expected output would be Foo-3\nFooBar-5
However, clang++ 3.8 gives me:
error: no matching function for call to 'doStuff'
doStuff<text1,3,text2,5>();
^~~~~~~~~~~~~~~~~~~~~~~~
test.cpp:7:6: note: candidate template ignored: invalid explicitly-specified argument for template
parameter 'REST'
void doStuff() {
^
test.cpp:13:6: note: candidate template ignored: invalid explicitly-specified argument for template
parameter 'N'
void doStuff() {
^
In C++17, you might do something like
template <char const * TEXT, unsigned int N>
void doStuff() {
std::cout << TEXT << "-" << N << std::endl;
}
template <auto v1, auto v2, auto ... values>
void doStuff()
{
std :: cout << v1 << "-" << v2 << std :: endl;
doStuff<values...>();
}
Currently you have to pack your values by pair:
template<const char* S, int N>
struct pairValue {
static constexpr const char* s = S;
static constexpr int n = N;
};
template <typename ... Ts>
void doStuff()
{
const int dummy[] = {0, ((std::cout << Ts::s << "-" << Ts::n << std::endl), 0)...};
static_cast<void>(dummy); // Avoid warning for unused variable.
}
And call it:
doStuff<pairValue<text1, 3>, pairValue<text2, 5>>();
You can use them as parameters to work around it:
#include <iostream>
#include<type_traits>
char static const text1[] = "Foo";
char static const text2[] = "FooBar";
constexpr void doStuff() {}
template <typename T, typename U, typename... O>
constexpr
std::enable_if_t<
std::is_same<T, const char *>::value
and std::is_same<U, int>::value
> doStuff(T str, U num, O... o) {
std :: cout << str << "-" << num << std :: endl;
doStuff(o...);
}
int main() {
doStuff(text1,3,text2,5);
return 0;
}

Variadic template argument size (not count)

In C++11 I can get agregated size of arguments in such way:
template<typename First, typename... Rest>
size_t getSize( const First& first, const Rest& ...rest )
{
return getSize(first) + getSize( rest... );
}
template <typename T>
size_t getSize( const T& )
{
return sizeof(T);
}
std::cout << "Size of arguments is: " << getSize( short(0), double(0.0) ) << std::endl;
Output will be: "Size of arguments is: 10".
Is there any analogy for old C++?
P.S. Better solution for C++11 is also welcome
For C++11 I would use this which is evaluated by compile time.
#include <iostream>
template<typename First, typename... Rest>
struct total_size_of
{
static constexpr size_t value = total_size_of<First>::value + total_size_of<Rest...>::value;
};
template <typename T>
struct total_size_of<T>
{
static constexpr size_t value = sizeof(T);
};
int main() {
std::cout << "Size of arguments is: " << total_size_of<short, double>::value << std::endl;
return 0;
}
For Pre-C++11 I would use something like this (due to the lack of variadic templates).
#include <iostream>
template <typename T>
struct custom_size_of
{
static const size_t value = sizeof(T);
};
template <>
struct custom_size_of<void>
{
static const size_t value = 0;
};
template <typename One, typename Two = void, typename Three = void, typename Four = void>
struct total_size_of
{
static const size_t value = custom_size_of<One>::value + custom_size_of<Two>::value + custom_size_of<Three>::value + custom_size_of<Four>::value;
};
int main()
{
std::cout << "Size of arguments is: " << total_size_of<short, double>::value << std::endl;
return 0;
}
While the C++11 version is still easily adjusted into a function that automatically determines the the parameter types, there is no nice solution of doing this with pre-C++.
C++11:
template <typename...Args>
size_t get_size_of(Args...args)
{
return total_size_of<Args...>::value;
}
Pre-C++11:
template <typename One>
static size_t get_size_of(const One&)
{
return total_size_of<One>::value;
}
template <typename One, typename Two>
static size_t get_size_of(const One&, const Two&)
{
return total_size_of<One, Two>::value;
}
template <typename One, typename Two, typename Three>
static size_t get_size_of(const One&, const Two&, const Three&)
{
return total_size_of<One, Two, Three>::value;
}
template <typename One, typename Two, typename Three, typename Four>
static size_t get_size_of(const One&, const Two&, const Three&, const Four&)
{
return total_size_of<One, Two, Three, Four>::value;
}

Get custom sizeof of std::function's variadic template argument types

I have a variadic template function that accepts std::function with variadic types. I want to find total sizeof of those types that std::function has, except that I want to treat double and float types as special, where sizeof(double) == 100 and sizeof(float) == 50.
Here is pseudo-code (doesn't compile)
#include <iostream>
#include <functional>
// terminating case for T=double
template<>
size_t getSize<double>()
{
return 100;
}
// terminating case for T=float
template<>
size_t getSize<float>()
{
return 50;
}
// terminating case for T being anything else
template<class T>
size_t getSize<T>()
{
return sizeof(T);
}
// recursive case
template<class T, class ...S>
size_t getSize<T, ...S>()
{
return getSize<T>() + getSize<S>();
}
template <class ...T>
void print_function_arg_custom_size(std::function<void(T...)> f)
{
size_t totalSize = 0;
// get size recursively
totalSize = getSize<T>();
std::cout << "totalSize: " << totalSize << std::endl;
}
void foo(uint8_t a, uint16_t b, uint32_t c, double d)
{
// noop
}
int main()
{
std::function<void(uint8_t, uint16_t, uint32_t, double)> f = foo;
print_function_arg_custom_size<uint8_t, uint16_t, uint32_t, double>(f);
return 0;
}
One of the issues I'm having with this is that getSize doesn't like my template specialization.
Couple of errors :
template<class T>
size_t getSize<T>()
{
return sizeof(T);
}
You don't need to provide a "specialization list" since it isn't a specialization. Also, since parameter packs can be empty, the compiler can't choose between the overloads getSize<T> and getSize<T, S...>. To fix it, make these changes:
template <typename T>
size_t getSize()
{
return sizeof(T);
}
// recursive case
template<class T, class U, class ...S>
size_t getSize()
{
return getSize<T>() + getSize<U, S...>();
}
Live Demo.

Function Overloading Based on Arbitrary Properties of Types doesn't work

In the example below, I need to extract some values. I have an efficient extractor, which can work with builtin types, and an inefficient template that can work with everything. To choose between these, I want to use Function Overloading Based on Arbitrary Properties of Types. Here is my code:
#include <string>
#include <iostream>
class extractor
{
public:
static void extract(const bool& val) { std::cout << "Specialized extractor called" << std::endl; }
static void extract(const double& val) { std::cout << "Specialized extractor called" << std::endl; }
};
template <typename T>
void extract_generic(const T& value) { std::cout << "Generic extractor called" << std::endl; }
template <typename T> struct is_extractor_native { static const bool val = false; };
template<> struct is_extractor_native<bool> {static const bool val = true; };
template<> struct is_extractor_native<double> {static const bool val = true; };
template <bool B, class T = void>
struct enable_if {
typedef T type;
};
template <class T>
struct enable_if<false, T> {};
template <typename T>
struct extract_caller
{
//line 32 below
static void extract(const T& val, typename enable_if<is_extractor_native<T>::val>::type * = 0)
{
extractor::extract(val);
}
//line 37 below
static void extract(const T& val, typename enable_if<!is_extractor_native<T>::val>::type * = 0)
{
extract_generic(val);
}
};
int main(void)
{
std::string string_value("hello");
double double_value(.123);
std::cout << "native extractor for std::string: " << (int)is_extractor_native<std::string>::val << std::endl;
std::cout << "native extractor for double: " << (int)is_extractor_native<double>::val << std::endl;
extract_caller<std::string>::extract(string_value);
extract_caller<double>::extract(double_value);
return 0;
}
When I build the compiler complains:
g++ main.cpp -o main
main.cpp: In instantiation of ‘extract_caller<std::basic_string<char> >’:
main.cpp:50:29: instantiated from here
main.cpp:32:14: error: no type named ‘type’ in ‘struct enable_if<false, void>’
main.cpp: In instantiation of ‘extract_caller<double>’:
main.cpp:51:24: instantiated from here
main.cpp:37:14: error: no type named ‘type’ in ‘struct enable_if<false, void>’
make: *** [main] Error 1
When comment out the extraction and have only the traits printed, I get correct results:
./main
native extractor for std::string: 0
native extractor for double: 1
On the error listing you can see that for double the compiler passes the prototype on line 32, goes to 37 and prints error. The question is, why the SFINAE principle is not applied here?
SFINAE only works when certain errors (like the ones you have) happen in declaration part. In your case they happen in definition. You have to rewrite your code so that enable_if is used in declaration, in your case of struct extract_caller.
The code I got by fixing the problem doublep mentioned:
struct extract_caller
{
template <typename T>
static void extract(const T& val, typename enable_if<(bool)is_extractor_native<T>::val>::type * = 0)
{
extractor::extract(val);
}
template <typename T>
static void extract(const T& val, typename enable_if<!(bool)is_extractor_native<T>::val>::type * = 0)
{
extract_generic(val);
}
};
This can be used as
extract_caller::extract(string_value);
extract_caller::extract(double_value);

Is it possible to print a variable's type in standard C++?

For example:
int a = 12;
cout << typeof(a) << endl;
Expected output:
int
C++11 update to a very old question: Print variable type in C++.
The accepted (and good) answer is to use typeid(a).name(), where a is a variable name.
Now in C++11 we have decltype(x), which can turn an expression into a type. And decltype() comes with its own set of very interesting rules. For example decltype(a) and decltype((a)) will generally be different types (and for good and understandable reasons once those reasons are exposed).
Will our trusty typeid(a).name() help us explore this brave new world?
No.
But the tool that will is not that complicated. And it is that tool which I am using as an answer to this question. I will compare and contrast this new tool to typeid(a).name(). And this new tool is actually built on top of typeid(a).name().
The fundamental issue:
typeid(a).name()
throws away cv-qualifiers, references, and lvalue/rvalue-ness. For example:
const int ci = 0;
std::cout << typeid(ci).name() << '\n';
For me outputs:
i
and I'm guessing on MSVC outputs:
int
I.e. the const is gone. This is not a QOI (Quality Of Implementation) issue. The standard mandates this behavior.
What I'm recommending below is:
template <typename T> std::string type_name();
which would be used like this:
const int ci = 0;
std::cout << type_name<decltype(ci)>() << '\n';
and for me outputs:
int const
<disclaimer> I have not tested this on MSVC. </disclaimer> But I welcome feedback from those who do.
The C++11 Solution
I am using __cxa_demangle for non-MSVC platforms as recommend by ipapadop in his answer to demangle types. But on MSVC I'm trusting typeid to demangle names (untested). And this core is wrapped around some simple testing that detects, restores and reports cv-qualifiers and references to the input type.
#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;
}
The Results
With this solution I can do this:
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';
}
and the output 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
Note (for example) the difference between decltype(i) and decltype((i)). The former is the type of the declaration of i. The latter is the "type" of the expression i. (expressions never have reference type, but as a convention decltype represents lvalue expressions with lvalue references).
Thus this tool is an excellent vehicle just to learn about decltype, in addition to exploring and debugging your own code.
In contrast, if I were to build this just on typeid(a).name(), without adding back lost cv-qualifiers or references, the output would be:
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
I.e. Every reference and cv-qualifier is stripped off.
C++14 Update
Just when you think you've got a solution to a problem nailed, someone always comes out of nowhere and shows you a much better way. :-)
This answer from Jamboree shows how to get the type name in C++14 at compile time. It is a brilliant solution for a couple reasons:
It's at compile time!
You get the compiler itself to do the job instead of a library (even a std::lib). This means more accurate results for the latest language features (like lambdas).
Jamboree's answer doesn't quite lay everything out for VS, and I'm tweaking his code a little bit. But since this answer gets a lot of views, take some time to go over there and upvote his answer, without which, this update would never have happened.
#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
}
This code will auto-backoff on the constexpr if you're still stuck in ancient C++11. And if you're painting on the cave wall with C++98/03, the noexcept is sacrificed as well.
C++17 Update
In the comments below Lyberta points out that the new std::string_view can replace 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
}
I've updated the constants for VS thanks to the very nice detective work by Jive Dadson in the comments below.
Update:
Be sure to check out this rewrite or this rewrite below which eliminate the unreadable magic numbers in my latest formulation.
Try:
#include <typeinfo>
// …
std::cout << typeid(a).name() << '\n';
You might have to activate RTTI in your compiler options for this to work. Additionally, the output of this depends on the compiler. It might be a raw type name or a name mangling symbol or anything in between.
According to Howard's solution, if you don't like the magic number, I think this is a good way to represent and it looks intuitive:
#include <string_view>
template <typename T>
constexpr auto type_name() {
std::string_view name, 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)";
#endif
name.remove_prefix(prefix.size());
name.remove_suffix(suffix.size());
return name;
}
Demo.
Very ugly but does the trick if you only want compile time info (e.g. for debugging):
auto testVar = std::make_tuple(1, 1.0, "abc");
decltype(testVar)::foo= 1;
Returns:
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*>'
Don't forget to include <typeinfo>
I believe what you are referring to is runtime type identification. You can achieve the above by doing .
#include <iostream>
#include <typeinfo>
using namespace std;
int main() {
int i;
cout << typeid(i).name();
return 0;
}
Note that the names generated by the RTTI feature of C++ is not portable.
For example, the class
MyNamespace::CMyContainer<int, test_MyNamespace::CMyObject>
will have the following names:
// MSVC 2003:
class MyNamespace::CMyContainer[int,class test_MyNamespace::CMyObject]
// G++ 4.2:
N8MyNamespace8CMyContainerIiN13test_MyNamespace9CMyObjectEEE
So you can't use this information for serialization. But still, the typeid(a).name() property can still be used for log/debug purposes
You can use templates.
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 the example above, when the type is not matched it will print "unknown".
As mentioned, typeid().name() may return a mangled name. In GCC (and some other compilers) you can work around it with the following 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;
}
Howard Hinnant used magic numbers to extract type name. 康桓瑋 suggested string prefix and suffix. But prefix/suffix keep changing.
With “probe_type” type_name automatically calculates prefix and suffix sizes for “probe_type” to extract type name:
#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;
}
Output
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 version 16.7.6:
class test
const int*&
unsigned int
const int
const int*
const int*&
class typeName::probe_type
Another take on #康桓瑋's answer (originally ), making less assumptions about the prefix and suffix specifics, and inspired by #Val's answer - but without polluting the global namespace; without any conditions; and hopefully easier to read.
The popular compilers provide a macro with the current function's signature. Now, functions are templatable; so the signature contains the template arguments. So, the basic approach is: Given a type, be in a function with that type as a template argument.
Unfortunately, the type name is wrapped in text describing the function, which is different between compilers. For example, with GCC, the signature of template <typename T> int foo() with type double is: int foo() [T = double].
So, how do you get rid of the wrapper text? #HowardHinnant's solution is the shortest and most "direct": Just use per-compiler magic numbers to remove a prefix and a suffix. But obviously, that's very brittle; and nobody likes magic numbers in their code. It turns out, however, that given the macro value for a type with a known name, you can determine what prefix and suffix constitute the wrapping.
#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);
}
See it on GodBolt. This should be working with MSVC as well.
You could use a traits class for this. Something like:
#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;
}
The DECLARE_TYPE_NAME define exists to make your life easier in declaring this traits class for all the types you expect to need.
This might be more useful than the solutions involving typeid because you get to control the output. For example, using typeid for long long on my compiler gives "x".
In C++11, we have decltype. There is no way in standard c++ to display exact type of variable declared using decltype. We can use boost typeindex i.e type_id_with_cvr (cvr stands for const, volatile, reference) to print type like below.
#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;
}
You may also use c++filt with option -t (type) to demangle the type name:
#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;
}
Tested on linux only.
The other answers involving RTTI (typeid) are probably what you want, as long as:
you can afford the memory overhead (which can be considerable with some compilers)
the class names your compiler returns are useful
The alternative, (similar to Greg Hewgill's answer), is to build a compile-time table of traits.
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";
};
Be aware that if you wrap the declarations in a macro, you'll have trouble declaring names for template types taking more than one parameter (e.g. std::map), due to the comma.
To access the name of the type of a variable, all you need is
template <typename T>
const char* get_type_as_string(const T&)
{
return type_as_string<T>::value;
}
As I challenge I decided to test how far can one go with platform-independent (hopefully) template trickery.
The names are assembled completely at compilation time. (Which means typeid(T).name() couldn't be used, thus you have to explicitly provide names for non-compound types. Otherwise placeholders will be displayed instead.)
Example usage:
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);};
As explained by Scott Meyers in Effective Modern C++,
Calls to std::type_info::name are not guaranteed to return anything sensible.
The best solution is to let the compiler generate an error message during the type deduction, for example:
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;
}
The result will be something like this, depending on the 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;
Hence, we get to know that x's type is int and y's type is const int*
A more generic solution without function overloading than my previous one:
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;}
Here MyClass is user defined class. More conditions can be added here as well.
Example:
#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;}
Output:
int
String
MyClass
I like Nick's method, A complete form might be this (for all basic data types):
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*"; }
#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;
}
Output:
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
For anyone still visiting, I've recently had the same issue and decided to write a small library based on answers from this post. It provides constexpr type names and type indices und is is tested on Mac, Windows and Ubuntu.
The library code is here: https://github.com/TheLartians/StaticTypeInfo
For something different, here's a "To English" conversion of the type, deconstructing every qualifier, extent, argument, and so on, recursively building the string describing the type I think the "deduced this" proposal would help cut down many of the specializations. In any case, this was a fun morning exercise, regardless of excessive bloat. :)
struct X {
using T = int *((*)[10]);
T f(T, const unsigned long long * volatile * );
};
int main() {
std::cout << describe<decltype(&X::f)>() << std::endl;
}
Output:
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
Here's the code:
https://godbolt.org/z/7jKK4or43
Note: most current version is in my github: https://github.com/cuzdav/type_to_string
// 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;
}
Copying from this answer: https://stackoverflow.com/a/56766138/11502722
I was able to get this somewhat working for C++ static_assert(). The wrinkle here is that static_assert() only accepts string literals; constexpr string_view will not work. You will need to accept extra text around the typename, but it works:
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 Output:
error C2338: Test failed on this used type: void __cdecl assertIfTestFailed<class BadType>(void)
... continued trace of where the erroring code came from ...
Building on a number of the previous answers, I made this solution which does not store the result of __PRETTY_FUNCTION__ in the binary. It uses a static array to hold the string representation of the type name.
It requires C++23.
#include <iostream>
#include <string_view>
#include <array>
template <typename T>
constexpr auto type_name() {
auto gen = [] <class R> () constexpr -> std::string_view {
return __PRETTY_FUNCTION__;
};
constexpr std::string_view search_type = "float";
constexpr auto search_type_string = gen.template operator()<float>();
constexpr auto prefix = search_type_string.find(search_type);
constexpr auto suffix = search_type_string.size() - prefix - search_type.size();
constexpr auto str = gen.template operator()<T>();
constexpr int size = str.size() - prefix - suffix;
constexpr auto static arr = [&]<std::size_t... I>(std::index_sequence<I...>) constexpr {
return std::array<char, size>{str[prefix + I]...};
} (std::make_index_sequence<size>{});
return std::string_view(arr.data(), size);
}
C++ Data type resolve in Compile-Time using Template and Runtime using TypeId.
Compile time solution.
template <std::size_t...Idxs>
constexpr auto substring_as_array(std::string_view str, std::index_sequence<Idxs...>)
{
return std::array{str[Idxs]..., '\n'};
}
template <typename T>
constexpr auto type_name_array()
{
#if defined(__clang__)
constexpr auto prefix = std::string_view{"[T = "};
constexpr auto suffix = std::string_view{"]"};
constexpr auto function = std::string_view{__PRETTY_FUNCTION__};
#elif defined(__GNUC__)
constexpr auto prefix = std::string_view{"with T = "};
constexpr auto suffix = std::string_view{"]"};
constexpr auto function = std::string_view{__PRETTY_FUNCTION__};
#elif defined(_MSC_VER)
constexpr auto prefix = std::string_view{"type_name_array<"};
constexpr auto suffix = std::string_view{">(void)"};
constexpr auto function = std::string_view{__FUNCSIG__};
#else
# error Unsupported compiler
#endif
constexpr auto start = function.find(prefix) + prefix.size();
constexpr auto end = function.rfind(suffix);
static_assert(start < end);
constexpr auto name = function.substr(start, (end - start));
return substring_as_array(name, std::make_index_sequence<name.size()>{});
}
template <typename T>
struct type_name_holder {
static inline constexpr auto value = type_name_array<T>();
};
template <typename T>
constexpr auto type_name() -> std::string_view
{
constexpr auto& value = type_name_holder<T>::value;
return std::string_view{value.data(), value.size()};
}
Runtime solution.
template <typename T>
void PrintDataType(T type)
{
auto name = typeid(type).name();
string cmd_str = "echo '" + string(name) + "' | c++filt -t";
system(cmd_str.c_str());
}
Main Code
#include <iostream>
#include <map>
#include <string>
#include <typeinfo>
#include <string_view>
#include <array> // std::array
#include <utility> // std::index_sequence
using std::string;
int main()
{
//Dynamic resolution.
std::map<int, int> iMap;
PrintDataType(iMap);
//Compile type resolution.
std::cout << type_name<std::list<int>>() << std::endl;
return 0;
}
Code Snippet
Consider this code:
#include <iostream>
int main()
{
int a = 2; // Declare type "int"
std::string b = "Hi"; // Declare type "string"
long double c = 3438; // Declare type "long double"
if(typeid(a) == typeid(int))
{
std::cout<<"int\n";
}
if(typeid(b) == typeid(std::string))
{
std::cout<<"string\n";
}
if(typeid(c) == typeid(long double))
{
std::cout<<"long double";
}
return 0;
}
I believe you want the whole word (rather than only printing the short form of int (which is i), you want int), that is why I did the if.
For some of the variables (string,long double etc... which do not print the expected result comparing their short forms), you need to compare the result of applying the typeid operator with the typeid of a specific type.
From cppreference:
Returns an implementation defined null-terminated character string containing the name of the type. No guarantees are given; in particular, the returned string can be identical for several types and change between invocations of the same program.
IMO, Python is better than C++ in this case. Python has built-in type function to directly access the data type of the variable.