static_assert - a way to dynamically customize error message - c++

Is there a way to make static_assert's string being dynamically customized and then displayed?
What I mean is something like:
//pseudo code
static_assert(Check_Range<T>::value, "Value of " + typeof(T) + " type is not so good ;)");

No, there is not.
However this does not matter so much, because static_assert are evaluated at compile-time, and in case of error the compiler will not only print out the message itself, but it will also print the instanciation stack (in case of templates).
Have a look at this synthetic example in ideone:
#include <iostream>
template <typename T>
struct IsInteger { static bool const value = false; };
template <>
struct IsInteger<int> { static bool const value = true; };
template <typename T>
void DoSomething(T t) {
static_assert(IsInteger<T>::value, // 11
"not an integer");
std::cout << t;
}
int main() {
DoSomething("Hello, World!"); // 18
}
The compiler does not only emits the diagnostic, but it also emits the full stack:
prog.cpp: In function 'void DoSomething(T) [with T = const char*]':
prog.cpp:18:30: instantiated from here
prog.cpp:11:3: error: static assertion failed: "not an integer"
If you know Python or Java and how they print the stack in case of exception, it should be familiar. In fact, though, it's even better, because you not only get the call stack, but you also get the arguments values (types here)!
Therefore, dynamic messages are not as necessary :)

The standard specifies the second argument of static_assert to be a string literal, so no chance for computation there as far as I can see (except for preprocessor macros).
A compiler could extend the standard and allow const-expressions of approporiate type in this position, but I have no idea if any compiler does.

As Matthieu said, it's not possible, but you can get some of the functionalities you're looking for by using macros:
#define CHECK_TYPE_RANGE(type)\
static_assert(Check_Range<type>::value, "Value of " #type " type is not so good ;)");
CHECK_TYPE_RANGE(float); // outputs "Value of float type is not so good ;)"

Related

Auto expanding macros

I try to expand my knowledge about macros and their usage.
I have a specific problem which i bumped.
Here's my situation
I have a class named RudyObject
This class has a get function for both member(GetRudyObjectID()) and static(GetStaticRudyObjectID()).
Here's my problem
I plan to create macro which adapts itself to given situation.
Here's the scenario i'd like to solve
TYPEOF(variable value type) should expand as variable.GetRudyObjectID()
TYPEOF(variable pointer) should expand as variable->GetRudyObjectID()
TYPEOF(type) should expand astype::GetStaticRudyObjectID()
Do you guys have any solution,tips or directios for this situation.
The first two you can easily get via a template with a specialization for pointers (via if constexpr) and the third is another overload only taking a template parameter:
#include <string>
#include <iostream>
template <typename T>
auto type_of(const T& t){
if constexpr (std::is_pointer_v<T>) {
return std::string{"a pointer \n"};
}
else {
return std::string{"not a pointer \n"};
}
}
template <typename T>
auto type_of() {
return std::string{"type_of without parameter \n"};
}
int main()
{
int x;
std::cout << type_of(x);
std::cout << type_of(&x);
std::cout << type_of<int>();
return 0;
}
Output:
not a pointer
a pointer
type_of without parameter
I advise you to not use macros for this. They have their place in automatic code generation, but using them to save a bit of typing will typically lead to hard to read, maintain and debug code when other C++ features can replace them easily without all those downsides. Moreover, macros are expanded before the code is compiled, ie the preprocessor does not know about types, pointers or variables. Macros are only about replacing tokens.

Determine if string contains a particular character at compile time

I'd like to determine if a string contains a particular character at compile time. I thought I would use std::string_view as it has constexpr methods to do what I want. This is the code:
#include <iostream>
#include <string_view>
using namespace std::literals;
template<std::size_t N>
constexpr bool ContainsAsterisk(const char(&formatString)[N])
{
constexpr std::string_view fmtString{ formatString, N - 1 }; // error C2131: expression did not evaluate to a constant
constexpr bool containsAsterisk = fmtString.find('*') != fmtString.npos;
return containsAsterisk;
}
int main()
{
if (ContainsAsterisk("sdf"))
{
std::cout << "sdf no\n";
}
if (ContainsAsterisk("er*r"))
{
std::cout << "er*r yes\n";
}
std::cout << "done\n";
}
This doesn't compile because of these errors
ConsoleApplication.cpp(9,41): error C2131: expression did not evaluate to a constant
ConsoleApplication.cpp(9,43): message : failure was caused by a read of a variable outside its lifetime
ConsoleApplication.cpp(9,43): message : see usage of 'formatString'
ConsoleApplication.cpp(17): message : see reference to function template instantiation 'bool ContainsAsterisk<4>(const char (&)[4])' being compiled
ConsoleApplication.cpp(10,37): error C2131: expression did not evaluate to a constant
ConsoleApplication.cpp(9,43): message : failure was caused by a read of a variable outside its lifetime
ConsoleApplication.cpp(9,43): message : see usage of 'formatString'
I've done quite a bit of googling, and can't understand what this error is telling me! I don't understand how the variable is being read outside it's lifetime, it's a literal (isn't it?) that I thought would be available at compile time.
Any tips explaining the error and how to fix would be appreciated. Thanks.
You are overcomplicating things. std::string_view can be constructed by const char*:
constexpr bool ContainsAsterisk(std::string_view view) {
// constexpr bool b = view.find('*') != view.npos; // ERROR
return view.find('*') != view.npos;
}
int main() {
constexpr bool b = ContainsAsterisk("123123*"); // OK
}
Why am I getting the error?
According to cppreference, a function can be constexpr if:
there exists at least one set of argument values such that an
invocation of the function could be an evaluated subexpression of a
core constant expression [...]
This means that it's not necessary for a constexpr function to always return a constexpr value, neither is it expected to always receive a constexpr argument. It only makes sure that for some specific set of arguments (a constexpr const char* in thie case), it will give a constexpr return value.
Therefore, a definition that assumes the argument is always constexpr (see ERROR line above) is invalid.
Your code can simply be written as:
template<std::size_t N>
constexpr bool ContainsAsterisk(const char(&formatString)[N])
{
for (auto c : formatString)
{
if (c == '*')
{
return true;
}
}
return false;
}
You don't actually need to use string_view.
Unfortunately I can't explain to you why string_view doesn't work.
You already have answers on how else to do this so I wont add anything more to that but this answer will address
I don't understand how the variable is being read outside it's lifetime, it's a literal (isn't it?) that I thought would be available at compile time.
Yes, a string literal is a compile time constant. The issue here is that a function parameter is not a compile time constant, even if it is initialized from one. To see why that is, lets start with the function
template <auto var>
auto foo()
{
if constexpr (var == 1)
return 1;
else
return 1.0;
}
This function template will have two different return types, but only one of them will ever be used in a specialization so you still follow the rule of one return type per function. Now if we allowed constexpr function parameters like
auto foo(constexpr int var)
{
if constexpr (var == 1)
return 1;
else
return 1.0;
}
This function will either return a int or a double, but you can't have one function that has different return types.

constexpr constructor not called as constexpr for implicit type conversion

I made some code which is capable of dispatching to a function based upon the call-site providing a string associated with a given function (via a tuple of function pointers and a parallel array). Instead of accepting a string directly, the dispatch function accepts a Callable type, where a const char* is convertible to a Callable.
The constructor of Callable is constexpr, and looks up a function from the noted tuple with a basic recursive search. I've verified that the constructor is capable of working correctly and creating a constexpr Callable (example included). Since the dispatch function receives the arguments to pass to the Callable's operator(), I know the expected function signature of the Callable's operator() at the time I create it.
I'm trying to perform two checks at compile-time, when they can be done at compile-time. First, I check that the provided string exists in the pre-defined array of strings, at all. Second, I check that the signature of the function associated with that string matches the expected signature from the tuple of function pointers. I create "friendly" error messages at compile-time by throw()'ing within the constexpr method that looks up the function.
I've verified that by creating a constexpr callable, I get the expected error messages at compile-time. This works. What doesn't work is getting compile-time messages if I use my Dispatcher directly, letting the call-site convert a string into a Callable. I know that when I use runtime parameters, my dispatch function won't be called in a constexpr context - I intentionally didn't make that function constexpr; the point is to call it with runtime values. But I thought that implicit conversions "happen at the call-site", not within the called function.
Therefore, I thought that in a call like dispatcher("one", 1) (which calls the first function with a parameter of 1) would look like: "one" gets converted to a Callable at the call-site, then a call gets made as dispatcher(Callable("one"), 1). That would mean that the constexpr constructor could be used, at least. In my experience, as long as you don't ignore the result of a constexpr call, the call is made as constexpr if it can be, else it is made as runtime. See Constexpr functions not called at compile-time if result is ignored. This isn't happening -- the conversion constructor is being called at runtime when the conversion happens within a call to my dispatch function!
Does anyone know of a way I can change my code to get the conversion constructor to be called at compile-time if it can be??? I found a totally different solution to solve this general class of problem in this post, but frankly I like the syntax of the code below better, if I could get it working.
I'm not going to include the above code in the body of this post, but rather include a more canonical example that demonstrates the behavior and also shows the behavior I saw in the post I referenced above, all-in-one.
Live demo of the below: https://onlinegdb.com/r1s1OE77v
Live demo of my "real" problem, if interested: https://onlinegdb.com/rJCQ2bGXw
First the "test fixtures":
// Modified from https://stackoverflow.com/a/40410624/12854372
// In a constexpr context, ContextIsConstexpr1(size_t) always
// simply sets _s to 1 successfully.
extern bool no_symbol_s_is_zero;
struct ContextIsConstexpr1 {
size_t _s;
constexpr ContextIsConstexpr1(size_t s) : _s(s ? 1 : no_symbol_s_is_zero) {}
};
// In a constexpr context, ContextIsConstexpr2(size_t) will cause
// a compile-time error if 0 is passed to the constructor
struct ContextIsConstexpr2 {
size_t _s;
constexpr ContextIsConstexpr2(size_t s) : _s(1) {
if(!s) {
throw logic_error("s is zero");
}
}
};
// Accept one of the above. By using a CONVERSION constructor
// and passing in a size_t parameter, it DOES make a difference.
ContextIsConstexpr1 foo(ContextIsConstexpr1 c) { return c; }
ContextIsConstexpr2 bar(ContextIsConstexpr2 c) { return c; }
Now the test code:
int main()
{
constexpr size_t CONST = 1;
#define TEST_OBVIOUS_ONES false
// ------------------------------------------------------------
// Test 1: result is compile-time, param is compile-time
// ------------------------------------------------------------
#if TEST_OBVIOUS_ONES
// Compile-time link error iif s==0 w/ any optimization (duh)
constexpr auto test1_1 = ContextIsConstexpr1(CONST);
cout << test1_1._s << endl;
// Compile-time throw iif s==0 w/ any optimization (duh)
constexpr auto test1_2 = ContextIsConstexpr2(CONST);
cout << test1_2._s << endl;
#endif
// ------------------------------------------------------------
// Test 2: result is runtime, param is compile-time
// ------------------------------------------------------------
// Compile-time link error iif s==0 w/ any optimization ***See below***
auto test2_1 = ContextIsConstexpr1(CONST);
cout << test2_1._s << endl;
// Runtime throw iif s==0 w/ any optimization
// NOTE: Throw behavior is different than extern symbol behavior!!
auto test2_2 = ContextIsConstexpr2(CONST);
cout << test2_2._s << endl;
// ------------------------------------------------------------
// Test 3: Implicit conversion
// ------------------------------------------------------------
// Compile-time link error if (1) s==0 w/ any optimization *OR* (2) s>0 w/ low optimization!!
// Note: New s>0 error due to implicit conversion ***See above***
auto test3_1 = foo(CONST);
cout << test3_1._s << endl;
// Runtime throw iif s==0 w/ any optimization
auto test3_2 = bar(CONST);
cout << test3_2._s << endl;
// ------------------------------------------------------------
// Test 4: result is ignored, param is compile-time
// ------------------------------------------------------------
// Compile-time link error w/ any 's' iif low optimization
// Note: no error w/ s==0 with high optimization, new error w/ s>0 by ignoring result ***See above***
ContextIsConstexpr1{CONST};
// Runtime throw iif s==0 w/ any optimization
ContextIsConstexpr2{CONST};
// ------------------------------------------------------------
// Get runtime input, can't optimize this for-sure
// ------------------------------------------------------------
#if TEST_OBVIOUS_ONES
size_t runtime;
cout << "Enter a value: ";
cin >> runtime;
// ------------------------------------------------------------
// Test 5: result is runtime, param is runtime
// ------------------------------------------------------------
// Compile-time link error w/ any 's' w/ any optimization (duh)
auto test5_1 = ContextIsConstexpr1(runtime);
cout << test5_1._s << endl;
// Runtime throw iif s==0 w/ any optimization (duh)
auto test5_2 = ContextIsConstexpr2(runtime);
cout << test5_2._s << endl;
// ------------------------------------------------------------
// Test 6: result is ignored, param is runtime
// ------------------------------------------------------------
// Compile-time link error w/ any 's' w/ any optimization (duh)
ContextIsConstexpr1{runtime};
// Runtime throw iif s==0 w/ any 's' w/ any optimization (duh)
ContextIsConstexpr2{runtime};
#endif
}
Does anyone know of a way I can change my code to get the conversion constructor to be called at compile-time if it can be
As I said in linked posted, call of constexpr functions at compile time is done only in constant expression.
Parameters are not constexpr.
One workaround would be to use MACRO:
#define APPLY_DISPATCHER(dispatcher, str, ...) \
do { \
constexpr callable_type_t<decltype(dispatcher), decltype(make_tuple(__VA_ARGS__))> callable(str); \
(dispatcher)(callable, __VA_ARGS__); \
} while (0)
with
template <typename Dispatcher, typename Tuple> struct callable_type;
template <typename Dispatcher, typename ... Ts>
struct callable_type<Dispatcher, std::tuple<Ts...>>
{
using type = typename Dispatcher::template Callable<Ts...>;
};
template <typename Dispatcher, typename Tuple>
using callable_type_t = typename callable_type<Dispatcher, Tuple>::type;
With usage:
APPLY_DISPATCHER(dispatcher, "one", 1);
APPLY_DISPATCHER(dispatcher, "a", 1); // Fail at compile time as expected
Demo.
But not really better than proposed dispatcher.dispatch(MAKE_CHAR_SEQ("a"), 1); (or with extension dispatcher.dispatch("a"_cs, 1);) (providing dispatch overload to be able to create constexpr Callable).

Is typeid of type name always evaluated at compile time in c++?

I wanted to check that typeid is evaluated at compile time when used with a type name (ie typeid(int), typeid(std::string)...).
To do so, I repeated in a loop the comparison of two typeid calls, and compiled it with optimizations enabled, in order to see if the compiler simplified the loop (by looking at the execution time which is 1us when it simplifies instead of 160ms when it does not).
And I get strange results, because sometimes the compiler simplifies the code, and sometimes it does not. I use g++ (I tried different 4.x versions), and here is the program:
#include <iostream>
#include <typeinfo>
#include <time.h>
class DisplayData {};
class RobotDisplay: public DisplayData {};
class SensorDisplay: public DisplayData {};
class RobotQt {};
class SensorQt {};
timespec tp1, tp2;
const int n = 1000000000;
int main()
{
int avg = 0;
clock_gettime(CLOCK_REALTIME, &tp1);
for(int i = 0; i < n; ++i)
{
// if (typeid(RobotQt) == typeid(RobotDisplay)) // (1) compile time
// if (typeid(SensorQt) == typeid(SensorDisplay)) // (2) compile time
if (typeid(RobotQt) == typeid(RobotDisplay) ||
typeid(SensorQt) == typeid(SensorDisplay)) // (3) not compile time ???!!!
avg++;
else
avg--;
}
clock_gettime(CLOCK_REALTIME, &tp2);
std::cout << "time (" << avg << "): " <<
(tp2.tv_sec-tp1.tv_sec)*1000000000+(tp2.tv_nsec-tp1.tv_nsec) <<
" ns" << std::endl;
}
The conditions in which this problem appear are not clear, but:
- if there is no inheritance involved, no problem (always compile time)
- if I do only one comparison, no problem
- the problem only appears only with a disjunction of comparisons if all the terms are false
So is there something I didn't get with how typeid works (is it always supposed to be evaluated at compilation time when used with type names?) or may this be a gcc bug in evaluation or optimization?
About the context, I tracked down the problem to this very simplified example, but my goal is to use typeid with template types (as partial function template specialization is not possible).
Thanks for your help!
I don't really know the answer to your question but if you use is_same<> metafunction instead of typeid you might get more desirable results. Even if you don't have access to this metafunction, it is very easy to write one:
template < typename T1, typename T2 >
struct is_same
{
enum { value = false }; // is_same represents a bool.
typedef is_same<T1,T2> type; // to qualify as a metafunction.
};
template < typename T >
struct is_same
{
enum { value = true };
typedef is_same<T,T> type;
};
typeid is part of the Run-Time Type Identification mechanism, which suggests what it's useful for: it's main usage is identifying the dynamic type of a pointer/reference to a base class at runtime. When the types are statically known at compile-time, you don't need to "identify" them as you already know what they are.
In the example, there is nothing to identify at runtime, though, yet the results are not in any way useful at compile-time (typeid cannot appear in const-expressions, which is what you need for template metaprogramming).
Therefore I also recommend is_same
For any type T, if T is polymorphic, the compiler is required to evaluate the typeid stuff at runtime. If T is non-polymorphic, the compiler is required to evaluate the typeid stuff at compile time. However, i cannot find the relevant reference in the C++ draft (n3000.pdf) for it.
Infact, in one of the projects that i worked on, this trick was used to find whether a class was polymorphic at runtime.
template <class T>
bool isPolymorphic() {
bool answer=false;
T *t = new T();
typeid(answer=true,*t);
delete t;
return answer;
}
I had asked a related question here on SO a few months back.

Stringify template arguments

Is it possible in C++ to stringify template arguments?
I tried this:
#include <iostream>
#define STRINGIFY(x) #x
template <typename T>
struct Stringify
{
Stringify()
{
std::cout << STRINGIFY(T) << endl;
}
};
int main()
{
Stringify<int> s;
}
But what I get is a T, and not an int. Seems that the preprocessor macros are evaluated before template instantiation.
Is there any other way to do this?
Is there any way for the preprocessing to take place after template instantiation? (Compiler is VC++).
You could try
typeid(T).name()
Edit: Fixed based on comments.
You could use some template magic.
#include <iostream>
template <typename T>
struct TypeName { static const char *name; };
template <typename T>
const char *TypeName<T>::name = "unknown";
template <>
const char *TypeName<int>::name = "int";
template <typename T>
struct Stringify
{
Stringify()
{
std::cout << TypeName<T>::name << std::endl;
}
};
int main()
{
Stringify<int> s;
}
This has an advantage over RTTI (i.e. typeinfo) - it is resolved during compilation; and disadvantage - you need to provide type information yourself (unless there is some library that does that already that I'm not aware of; maybe something in Boost even).
Or, as Martin York suggested in comments, use inline function templates instead:
template <typename T>
inline const char* typeName(void) { return "unknown"; }
template <>
inline const char* typeName<int>(void) { return "int"; }
// ...
std::cout << typeName<T>() << std::endl;
But, if you'll ever need to store more information about that particular type, then class templates will probably be better.
Your code doesn't work because the preprocessor, responsible for searching and expanding the macros you use in your code, is not aware of the language itself. It is just a text parser. It finds that STRINGIFY(T) in the very function template and expand it, much before you give a type to that template. As it turns out, you will always get "T" instead of the typename you expected, unfortunately.
As litb suggested, I've (badly) implemented this `getTypeName' function template that returns the typename you pass it:
#include <iostream>
template <typename _Get_TypeName>
const std::string &getTypeName()
{
static std::string name;
if (name.empty())
{
const char *beginStr = "_Get_TypeName =";
const size_t beginStrLen = 15; // Yes, I know...
// But isn't it better than strlen()?
size_t begin,length;
name = __PRETTY_FUNCTION__;
begin = name.find(beginStr) + beginStrLen + 1;
length = name.find("]",begin) - begin;
name = name.substr(begin,length);
}
return name;
}
int main()
{
typedef void (*T)(int,int);
// Using getTypeName()
std::cout << getTypeName<float>() << '\n';
std::cout << getTypeName<T>() << '\n'; // You don't actually need the
// typedef in this case, but
// for it to work with the
// typeid below, you'll need it
// Using typeid().name()
std::cout << typeid(float).name() << '\n';
std::cout << typeid(T).name() << '\n';
return 0;
}
The code above results in the following output with GCC flag -s ("strip all symbols from binary") enabled:
float
void (*)(int, int)
f
PFviiE
So, you see, getTypename() does a fairly better job, at the cost of that fugly string parsing hack (I KNOW, it's damn ugly).
A few points to take into account:
The code is GCC only. I don't know how to port it to another compiler. Probably only a few others have such a facility to produce so pretty function names, and from what I searched, MSVC++ doesn't have one, if you're asking yourself that.
If, in a new version, GCC formats __PRETTY_FUNCTION__'s differently, the string matching can break and you'll have to fix it. For this same reason I also warn that getTypeName() might be good for debugging (and, still, maybe not even good for that), but it is surely bad, bad, and bad for other purposes such as comparing two types in a template or something like that (I don't know, just guessing what someone might think of..). Use it solely for debugging, and preferentially don't call it in release builds (use macros to disable), so that you don't use __PRETTY_FUNCTION__ and thus the compiler doesn't produce the string for it.
I'm definitely no expert, and I'm not sure whether some odd type could cause the string matching to fail. I'd like to ask for people who read this post to comment if they know of such a case.
The code uses a static std::string. It means that, if some exception is thrown from its constructor or destructor, there is no way that it will reach a catch block and you'll get an unhandled exception. I don't know whether std::strings can do that, but beware that, if they do, you're potentially in trouble. I used it because it needs a destructor to free the memory. You could implement your own class for that, though, ensuring no exception is thrown besides allocation failure (that's pretty much fatal, isn't it? So...), and return a simple C-string.
With typedefs you can get some weird results, like this (for some reason, the site breaks the formatting of this snippet, so I'm using this paste link): http://pastebin.com/f51b888ad
Despite those disadvantages, I'd like to say that it sure is fast. For the second time you lookup for one same type name, it will cost picking a reference to a global std::string containing the name. And, comparatively to the template specialiazation methods suggested before, there is nothing else you have to declare besides the very template itself, so it is really much easier to use.
No, you cannot work on types as if they were variables. You could write code that extracted the typeid() of an element and printed the name, but the resulting value will probably not be what you expect (type names are not standarized).
You can also work with template specializations (and some macro magic) to achieve a more interesting version if the number of types you want to work with is limited:
template <typename T> const char* printtype(); // not implemented
// implement specializations for given types
#define DEFINE_PRINT_TYPE( type ) \
template<>\
const char* printtype<type>() {\
return #type;\
}
DEFINE_PRINT_TYPE( int );
DEFINE_PRINT_TYPE( double );
// ... and so on
#undef DEFINE_PRINT_TYPE
template <typename T> void test()
{
std::cout << printtype<T>() << std::endl;
}
int main() {
test<int>();
test<double>();
test<float>(); // compilation error, printtype undefined for float
}
Or you could even combine both versions: implement the printtype generic template using typeinfo and then provide specializations for the types you want to have fancier names.
template <typename T>
const char* printtype()
{
return typeid(T).name();
}
This breaks one of my primary tenets of C++ code writing: Avoid using tricks in both the template features and the preprocessor at the same time.
Part of the reason for templates and the nastiness they introduce into the language was an attempt to wean developers away from using the preprocessor. If you use both, then the terrorists win.
If you use boost/core/demangle.hpp, you can get a reliable human-readable string.
char const * name = typeid(T).name();
boost::core::scoped_demangled_name demangled( name );
std::cout << (demangled.get() ? demangled.get() : "Failed to demangle") << std::endl;
in my code I use the "awful" double-declaration of the "Class-Name"
MqFactoryC<MyServer>::Add("MyServer").Default();
because c++ is NOT able to extract the string "MyServer" from the template…
the only "way" to get "rid" of this… using a cpp "wrapper"
#define MQ_CPPSTR(s) #s
#define MqFactoryCAdd(T) MqFactoryC<T>::Add(MQ_CPPSTR(T)).Default()
Here’s what I do: I have a demangle() function (implemented on top of abi::__cxa_demangle() which I call with a couple of convenience template function overloads, nameof(), with either the type I want stringified or an instance of same.
It’s fairly compact, so I’ll reproduce it here in all its glory. In demangle.hh we have:
#pragma once
#include <typeinfo>
namespace terminator {
/// actual function to demangle an allegedly mangled thing
char const* demangle(char const* const symbol) noexcept;
/// convenience function template to stringify a name of a type,
/// either per an explicit specialization:
/// char const* mytypename = terminator::nameof<SomeType>();
template <typename NameType>
char const* nameof() {
try {
return demangle(typeid(NameType).name());
} catch (std::bad_typeid const&) {
return "<unknown>";
}
}
/// … or as implied by an instance argument:
/// char const* myinstancetypename = terminator::nameof(someinstance);
template <typename ArgType>
char const* nameof(ArgType argument) {
try {
return demangle(typeid(argument).name());
} catch (std::bad_typeid const&) {
return "<unknown>";
}
}
} /* namespace terminator */
… And then in demangle.cpp:
#include "demangle.hh"
#include <cstdlib>
#include <cxxabi.h>
#include <mutex>
#include <memory>
namespace terminator {
namespace {
/// define one singular, private, static std::mutex,
/// to keep the demangler from reentering itself
static std::mutex mangle_barrier;
/// define a corresponding private and static std::unique_ptr,
/// using a delete-expression to reclaim the memory malloc()'ed by
/// abi::__cxa_demangle() upon its return.
/// … we use clang pragmas to add flags locally for this to work:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wglobal-constructors"
#pragma clang diagnostic ignored "-Wexit-time-destructors"
std::unique_ptr<char, decltype(std::free)&> demangled_name{ nullptr, std::free };
#pragma clang diagnostic pop
}
char const* demangle(char const* const symbol) noexcept {
if (!symbol) { return "<null>"; }
std::lock_guard<std::mutex> lock(mangle_barrier);
int status = -4;
demangled_name.reset(
abi::__cxa_demangle(symbol,
demangled_name.get(),
nullptr, &status));
return ((status == 0) ? demangled_name.release() : symbol);
}
} /* namespace terminator */
To use this, I think you’ll have to link to libc++ (or whatever your local equivalent is) to use abi::__cxa_demangle(). What may be suboptimal for the OP is the fact that this does the demangling and stringification at runtime. I’d personally love something constexpr-friendly in leu of this, but since I suffer from a severe macro-abuse allergy, I find this to be the least generally-unreasonable solution to this problem.
(the terminator namespace is inconsequential – I use this code in a libunwind-based stacktracer called from termination handler – feel free to s///g that token)