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
Pointer-To-Pointer Dynamic Two-Dimensional Array
How to Get the Real and Total Length of Char * (Char Array)
Conversion Function for Error Checking Considered Good
How to Connect MySQL Database Using C++
How to Make This C++ Object Non-Copyable
How to Safely Use Openmp with C++11
Passing Integers as Constant References Versus Copying
How Much Footprint Does C++ Exception Handling Add
How to Construct a Std::String from a Std::Vector<Char>
C++ Compile Time Error: Expected Identifier Before Numeric Constant
Initialization: Parenthesis VS. Equals Sign
How to Make Visual Studio Use the Native Amd64 Toolchain
C++ Inheritance - Inaccessible Base
Getting "Source Type Is Not Polymorphic" When Trying to Use Dynamic_Cast
How to Create a Sparse Array in C++