Harry Potter and the Type Name in Compiletime

I'm trying to figure out why the code doesn't compile.

I'm trying to figure out why the code doesn't compile.

A couple of years ago I wrote an article about getting the names of enum elements in my favorite pros without using typeidmacros and black magic, or even in compile time. Although no, there was still a little magic there. It was an interesting experience, but I did not find much use in production, although colleagues began to actively use this feature to iterate over enum in search of the desired element by its string representation. Of course, it was intended the other way around, but as they say, you can't put toothpaste back into the tube, they use it and that's a joy. And then in my home game engine I needed a similar functionality for getting the name of a structure or class in compile time, of course it could be done via typeid, but in the release build rtti is planned to be disabled, so this option is not suitable. But I still want to convert the name of the structure to a string. What does Harry have to do with it and why is all this needed at the end of the article.


And since there are no strict limits on not using the standard library in a home project, the capabilities of ready-made containers like array/string_view greatly simplified the code and the structure of the entire solution. Getting the type name in C++ is a real headache, it would seem that what is known to the compiler at the stage of building the project should be easily obtained by some built-in function like __builtin_typename, but this, as you understand, is not there, and will not be provided in future standards either. The closest way to get the type name is to use std::type_info::namewhich does not exist at the compilation stage, because the type table with which this structure works has not yet been assembled. And in general type_info does not guarantee that the result will be human readable.

If you're wondering how I got here __PRETTY_FUNCTION__this is described in detail in the previous article. I will not repeat myself, I will start from the place where it is possible to get human-readable information in relatively any place of the code. If you wrap the required type in a template function, adding a secret call inside and save the result somewhere, then the type name is clearly visible.

Programmers play with templates

Programmers play with templates

template <typename T>
constexpr std::string_view type_name_to_str() {
    std::string_view function = __PRETTY_FUNCTION__;

    std::string_view prefix = "constexpr std::string_view type_name_to_str() [with T = ";
    std::string_view suffix = "]";

    auto start = function.find(prefix) + prefix.size();
    auto end = function.rfind(suffix);

    return function.substr(start, end - start);
}

int main() {
    std::cout << "Type name: " << type_name_to_str<int>() << std::endl;
    std::cout << "Type name: " << type_name_to_str<double>() << std::endl;
    std::cout << "Type name: " << type_name_to_str<std::string>() << std::endl;
}

You can play here (godbolt), we are not interested in the output of the clang for now, the main thing is that it also allows you to pull off such a trick. Cutting off the prefix and postfix is ​​not a very difficult task, so the entire code is given at once. I will separately remind you that the given logic works both with and without optimizations, this is important because the compiler could easily throw out unnecessary information like that which returns __PRETTY_FUNCTION__ in the release, we will replace the type name with the first set of characters that comes to mind.

x86-64 gcc (trunk)
Program returned: 0
Program stdout
Type name: int; std::string_view = std::basic_string_view<char>
Type name: double; std::string_view = std::basic_string_view<char>
Type name: std::__cxx11::basic_string<char>; std::string_view = std::basic_string_view<char>


x86-64 clang (trunk)
Program returned: 139
Program stderr
terminate called after throwing an instance of 'std::out_of_range'
  what():  basic_string_view::substr: __pos (which is 52) > __size (which is 42)
Program terminated

Unfortunately, there is no way to create a constexpr string, so you'll have to do it through std::array, which can be initialized in compile time. But you can't just pass a string there either, because std::array is initialized element by element, and the output __PRETTY_FUNCTION__ This const char * in fact. In a string, even constexpr, you can access individual elements by index. If you use this property, you can split the string into individual characters, and then send the resulting string to the std::array constructor.

So let's put it all together, and to make it all work without a sheet of code, we'll use the compiler's ability to automatically infer the types of template arguments. And we'll generate the indexes via std::make_index_sequence<N>where N is the length of the original string. The array here acts as an intermediate storage, at the end of which there is a terminal symbol, unfortunately I have not found a more beautiful way to form a string.

std::index_sequence<Idxs...> - здесь будут лежать индексы символов в строке
std::string_view - здесь будет лежать сами данных из __PRETTY_FUNCTION__ 
std::array - сюда через конструктор мы положим данные из строки поэлементно

template <size_t ... Idxs>
constexpr auto str_to_array(std::string_view str, std::index_sequence<Idxs...>) {
  return std::array{ str[Idxs]..., '\0' };
}
Sometimes it compiles complete crap

Sometimes it compiles complete crap

Returning to the very first code example from the article, you can add a little to it to be able to get the desired string in normal form. (godbolt).

template <typename T>
constexpr auto type_name_str()
{
  constexpr auto suffix   = "]";
  constexpr auto prefix   = std::string_view{"with T = "};
  constexpr auto function = std::string_view{__PRETTY_FUNCTION__};

  constexpr auto start = function.find(prefix) + prefix.size();
  constexpr auto end = function.rfind(suffix);

  constexpr auto name = function.substr(start, (end - start));
  return str_to_array(name, std::make_index_sequence<name.size()>{});
}

int main() {
    std::cout << (char*)type_name_str<std::string>().data() << std::endl;
}

The obvious downside of this solution is the inconvenient call syntax; to bring it closer to the syntax of the standard library and help the compiler cache the types already found, you need to add syntactic sugar and bring it to a more familiar form (godbolt)

template <typename T>
struct type_name_holder {
  static inline constexpr auto value = type_name_str<T>();
};

template <typename T>
constexpr std::string_view type_name() {
  constexpr auto& value = type_name_holder<T>::value;
  return std::string_view{value.data(), value.size()};
}

int main() {
    std::cout << type_name<std::string>() << std::endl;
}

Why is this necessary at all?

In my free time I restore the game and the engine of the old one city ​​builder Pharaohfinally got to the interface of advisers, and here, dear habrazhitel, I wanted something strange – auto-registration of classes of windows of advisers and overloading of the interface, and in general any properties, on the fly in runtime. Hotload of plus structures from configs is beyond the scope of this article, but in order to know the properties of what type have changed, you need to have the name of this type somewhere in the fields of the class. You can do it for example manually, something like this, at first it was like this:

namespace ui {
struct advisor_ratings_window : public advisor_window {
    static constexpr inline const char * TYPEN = "advisor_ratings_window";
    virtual int handle_mouse(const mouse *m) override { return 0; }
...

By using the code described above, you can do less manual work and bring it to the form.

struct advisor_window : public ui::widget {
    bstring128 section;
    advisor_window(pcstr s) : section(s) {
...

template<typename T>
struct advisor_window_t : public advisor_window {
    inline advisor_window_t() : advisor_window(type_name<T>().data()) {
...

struct advisor_ratings_window : public advisor_window_t<advisor_ratings_window> {
    virtual int handle_mouse(const mouse *m) override
...

As a result, we get a structure that, with minimal manual intervention, stores its type, by which we can associate it in the game configs, and, for example, support hotload by type name.

advisor_ratings_window = {
  ui : {
		background 		 : outer_panel({size:[40, 27]}),
		background_image : image({pack:PACK_UNLOADED, id:2, pos :[60, 38]}),
...
Hidden text

By the way, if anyone remembers Zeus: Master of Olympus game
it's been a while for him too opened an open-source portI crossed my fingers that the guys will have enough patience to continue working on the project after 5 years.

What does Harry have to do with it?

I finally got around to reading this work and from the first pages I can't shake the impression that the magic of Hogwarts and the process of compiling templates in C++ are somewhere on the same layer of the universe. As in magic, if you formulate the “spell” incorrectly, the result can be completely different than expected. Just the other day, one of the partners launched a couple of Ogres into our basement, we have been unable to catch them for three days, even if the entire basement has been turning away for these days.

Compiled!

Compiled!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *