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    utilities::stopwatch         sw_default;
    utilities::precise_stopwatch sw_precise;
    utilities::steady_stopwatch  sw_steady;
    utilities::system_stopwatch  sw_system;

2    std::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).

Back to top