How to Obtain C++ Type Names in a Constexpr Way

Can I obtain C++ type names in a constexpr way?

Edit: Updated based on this answer to the non-constexpr-specific question; it is the result of refinements by several people including @HowardHinnant, @康桓瑋 @Val and myself.

The language standard does not - to my knowledge - provide any facility for obtaining type names. So, we resort to compiler-specific approaches. This works with GCC, clang and MSVC.

#include <string_view>
// If you can't use C++17's standard library, you'll need to use the GSL
// string_view or implement your own struct (which would not be very difficult,
// since we only need a few methods here)

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);
}

Getting type names at compile time in C++

I can't see typeid(T).name() incurring a runtime overhead. typeid(expr) yes, if expr is of a polymorphic type.

It looks like the demangling probably happens at runtime, but there's not an awful lot you can do about that. If this is only for debugging then I really wouldn't worry about it too much unless your profiler indicates that this is causing your program to slow down so much that debugging other elements of it is troublesome.

Determining specific type to use inside if constexpr

constexpr implies const in the context of variable declaration. Try this:

if constexpr (std::is_same<decltype(atype), const aT>::value)
std::cout << "I am type " << typeid(atype).name() << std::endl;

compile time typeid for every type

You could use some tricks as shown in this answer.

There's even a library called ctti that utilizes the same trick, it should work out of the box

static_assert(ctti::type_id<int>() != ctti::type_id<float>(), "compile-time type-id comparison");

constexpr auto hash = ctti::type_id<int>().hash();

How to check a type has constexpr constructor

I suppose you can use SFINAE together with the power of the comma operator

Following your idea, you can rewrite your f() functions as follows

template <typename T, int = (T{}, 0)>
constexpr bool f (int)
{ return true; }

template <typename>
constexpr bool f (long)
{ return false; }

Observe the trick: int = (T{}, 0) for the second template argument

This way f() is enabled (power of the comma operator) only if T{} can be constexpr constructed (because (T{}, 0) is the argument for a template parameter), otherwise SFINAE wipe away the first version of f().

And observe that the fist version of f() receive an unused int where the second one receive a long. This way the first version is preferred, when available, calling f() with an int; the second one is selected, as better than nothing solution, when the first one is unavailable (when the first template argument isn't constexpr default constructible).

Now you can construct two template constructors for foo that you can alternatively enable/disable according the fact the template parameter T (defaulted to A) is or isn't constexpr constructible

template <typename T = A,
std::enable_if_t<f<T>(0), std::nullptr_t> = nullptr>
constexpr foo() { std::cout << "constexpr" << std::endl; }

template <typename T = A,
std::enable_if_t<not f<T>(0), std::nullptr_t> = nullptr>
constexpr foo() { std::cout << "not constexpr" << std::endl; }

The following is a full compiling example (C++14 or newer, but you can modify it for C++11):

#include <iostream>
#include <type_traits>

template <typename T, int = (T{}, 0)>
constexpr bool f (int)
{ return true; }

template <typename>
constexpr bool f (long)
{ return false; }

template <typename A>
struct foo
{
template <typename T = A,
std::enable_if_t<f<T>(0), std::nullptr_t> = nullptr>
constexpr foo() { std::cout << "constexpr" << std::endl; }

template <typename T = A,
std::enable_if_t<not f<T>(0), std::nullptr_t> = nullptr>
constexpr foo() { std::cout << "not constexpr" << std::endl; }
};

struct X1 { constexpr X1 () {} };
struct X2 { X2 () {} };

int main()
{
foo<X1> f1; // print "constexpr"
foo<X2> f2; // print "not constexpr"
}

Is it possible with C++20 to have a constexpr function return a tuple of types that have static constexpr array's with a value passed though a macro?

It is possible to store a string literal inside of a template argument. I wrote a class that does exactly that a year or so ago. This allows the tuple creation to be constexpr while also offering a quite nice way of passing the strings. This class can also be used to differentiate between two types using a string without modifying the rest of the templated class, which is very useful if you're using typeless handles.

template <std::size_t N>
struct TemplateStringLiteral {
char chars[N];

consteval TemplateStringLiteral(const char (&literal)[N]) {
// Does anyone know of a replacement of std::copy_n? It's from
// <algorithm>.
std::copy_n(literal, N, chars);
}
};

Using this class, it is possible for us to pass the tuple creation this class with a string literal attached to it. This does, sadly, slightly modify your C::x from using the MEMBER define for the function arguments to using the MEMBER define for the template arguments. It is also perfectly possible to just use normal string literals in the template arguments.

struct C
{
// Essentially just f<"abc", "def">();
static constexpr auto x = f<MEMBER(abc), MEMBER(def)>();
};

For the function f we now want to take the parameters as template arguments. So we'll now use the TemplateStringLiteral class to take in the string literals, while also making it a template parameter pack to allow for any amount of parameters.

template <TemplateStringLiteral... literal>
consteval auto f() {
// I'll talk about A in a second. It just holds our string
return std::tuple<A<literal>...> {};
}

Now that we've got a function f that can create a std::tuple from some amount of string literals passed in through template parameters, we'll just need to define the class A that the tuple holds. We can't pass the string literals directly to std::tuple as it does not have something akin to TemplateStringLiteral. Defining class A is very simple, as we only need a str field holding our string literal.

template<TemplateStringLiteral literal>
struct A {
static constexpr auto str = std::to_array(literal.chars);
};

So, using TemplateStringLiteral we've got an implementation that's about 16 lines of C++ and is pretty easily understandable imo.



Related Topics



Leave a reply



Submit