In C++ 20
You can use the standard std::source_location
where its static method ::current
is consteval
in which you can use it at compile-time and then you can obtain the function_name
method.
template <typename T>
consteval auto func_name() {
const auto& loc = std::source_location::current();
return loc.function_name();
}
template <typename T>
consteval std::string_view type_of_impl_() {
constexpr std::string_view functionName = func_name<T>();
// since func_name_ is 'consteval auto func_name() [with T = ...]'
// we can simply get the subrange
// because the position after the equal will never change since
// the same function name is used
// another notice: these magic numbers will not work on MSVC
return {functionName.begin() + 37, functionName.end() - 1};
}
template <typename T>
constexpr auto type_of(T&& arg) {
return type_of_impl_<decltype(arg)>();
}
template <typename T>
constexpr auto type_of() {
return type_of_impl_<T>();
}
Note: The function name from the source location object may vary from compiler-to-compiler and you can use the macro __PRETTY_FUNCTION__
or any other related macros if your compiler doesn't yet support the source location library.
Usage:
int x = 4;
// type_of also preserves value category and const-qualifiers
// note: it returns std::string_view
type_of(3); // int&&
type_of(x); // int&
type_of(std::as_const(x)); // const int&
type_of(std::move(x)); // int&&
type_of(const_cast<const int&&>(x)); // const int&&
struct del { del() = delete; };
type_of<del>(); // main()::del (if inside main function)
// type_of(del{}); -- error
type_of<int>(); // int
type_of<const int&>(); // const int&
type_of<std::string_view>(); // std::basic_string_view<char>
type_of([]{}); // main()::<lambda()>&&
type_of<decltype([]{})>(); // main()::<lambda()>
type_of<std::make_index_sequence<3>>(); // std::integer_sequence<long unsigned int, 0, 1, 2>
// let's assume this class template is defined outside main function:
template <auto X> struct hello {};
type_of<hello<1>>(); // hello<1>
type_of<hello<3.14f>>(); // hello<3.1400001e+0f>
// also this:
struct point { int x, y; };
type_of<hello<point{.x = 1, .y = 2}>>() // hello<point{1, 2}>
Advantage of using this type_of
over demangling in typeid(...).name()
:
(also noted: I didn't test other compiler's ability, so I only guarantee for GCC)
- You can check the value at compile-time, such that
static_assert(type_of(4.0) == "double&&")
is valid.
- There is no runtime overhead.
- The operation can be done either at runtime or compile-time (depending on the argument given whether it's usable in a constant expression).
- It preserves cv-ref traits (
const
, volatile
, &
and &&
).
- You can alternatively use the template argument just in case the type's constructor is deleted and test without the cv-ref traits.