The Type of an Object
Introduction
The <utilities/type.h>
heeder defines functions that return a string representing an object’s “type” as the compiler/preprocessor sees.
template<typename T>
1constexpr std::string_view utilities::type();
template<typename T>
2constexpr std::string_view utilities::type(const T&);
- 1
-
Returns a string for a type, e.g.,
utilities::type<int>
will return"int"
. - 2
- Returns a string for the specific object type. For example, if x is an int, utilities::type(x) will return “int.”
There is no portable way to get a readable string representation of a type in C++, though clearly, the compiler has the information.
You can use std::typeid
to retrieve the get std::type_info
that has a name
field. However, that name
field is not standardized across compilers, and in some cases, it contains a mangled name, which is not easily readable Moreover, std::typeid
is not a compile-time call, so not useful for template meta-programming.
We can instead use the compiler’s preprocessor. It generally has some predefined macro that produces a clean-looking signature string when invoked in any function. The name of the macro we want isn’t standardized, but clang
and GCC
both use __PRETTY_FUNCTION__
while Microsoft uses __FUNCSIG__
. How the macro expands isn’t standardized but will be consistent with any compiler.
The macro string is available at compile time as its expansion is part of the pre-compilation phase. Using other constexpr
functions, you can parse the string to get a printable name for any type at compile time.
The type name will be perfectly readable but not identical across compilers. |
Example
#include <utilities/print.h>
#include <utilities/macros.h>
#include <utilities/stopwatch.h>
#include <utilities/type.h>
int main()
{
1::stopwatch sw_default;
utilities::precise_stopwatch sw_precise;
utilities::steady_stopwatch sw_steady;
utilities::system_stopwatch sw_system;
utilities
2std::cout << "Compiler: " << COMPILER_NAME << '\n';
std::print("sw_default has type '{}'\n", utilities::type(sw_default));
std::print("sw_precise has type '{}'\n", utilities::type(sw_precise));
std::print("sw_steady has type '{}'\n", utilities::type(sw_steady));
std::print("sw_system has type '{}'\n", utilities::type(sw_system));
}
- 1
-
See
stopwatch.h
for details. - 2
-
See
macros.h
for details.
Output from GCC
Compiler: gcc 13.2.0
sw_default has type 'stopwatch<std::chrono::_V2::system_clock>'
sw_precise has type 'stopwatch<std::chrono::_V2::system_clock>'
sw_steady has type 'stopwatch<std::chrono::_V2::steady_clock>'
sw_system has type 'stopwatch<std::chrono::_V2::system_clock>'
It seems that libstdc++
, the standard library for gcc
, only has one clock, namely std::chrono::system_clock
. The other clocks in its std::chrono
must all be aliases for that one.
Output from MSVC
Compiler: MSC 193131104
sw_default has type 'class utilities::stopwatch<struct std::chrono::steady_clock>'
sw_precise has type 'class utilities::stopwatch<struct std::chrono::steady_clock>'
sw_steady has type 'class utilities::stopwatch<struct std::chrono::steady_clock>'
sw_system has type 'class utilities::stopwatch<struct std::chrono::system_clock>'
This version of Microsoft Visual Studio Code also uses a single clock, std::chrono::steady_clock
for our system.
Output from clang
Compiler: clang 17.0.6
sw_default has type 'utilities::stopwatch<>'
sw_precise has type 'utilities::stopwatch<>'
sw_steady has type 'utilities::stopwatch<>'
sw_system has type 'utilities::stopwatch<std::chrono::system_clock>'
The specific clock type is not printed for the first three objects.
We have observed that while clang
uses the same __PRETTY_FUNCTION__
macro name as gcc
, its implementation is different, and it never outputs template arguments that match a default.
For the first three objects above, clang
outputs utilities::stopwatch<>
without any reference to the underlying clock. We conclude that all three use the default specified in stopwatch.h
, std::chrono::high_resolution_clock
. For this compiler, then std::chrono:steady_clock
is the same asstd::chrono::high_resolution_clock
.
However, the type name for the final sw_system
object references a different std::chrono::system_clock
. The standard library libc++
for clang
is able to access two different clocks (or at least two that it thinks are different).