0

Trying to do implementation of such function that will produce it's class name in string form without typing it manually. found that PRETTY_FUNCTION may do the job, and msvc 2022 says that binary | no operator found which takes left hand operand of type std::string_view. How to fix?

static constexpr std::string_view ClassName() {
        constexpr std::string_view pretty_function = std::source_location::current().function_name();
        consteval auto tokens = pretty_function | std::ranges::views::split([](std::string_view view) { return view == " "; })
            | std::ranges::views::transform([](const std::string_view&& token) {
            return token.compare("class") == 0 ? std::string_view{} : token;
        });
        for (const auto && token : tokens) {
            if (token.find('<') != std::string_view::npos) {
                return token.substr(0, token.find('<'));
            }
        } return tokens.back();
    }
Mat
  • 202,337
  • 40
  • 393
  • 406
Vitaly Protasov
  • 161
  • 1
  • 10
  • `consteval auto tokens = ...` doesn't seem right. Should be `constexpr` I guess? – Ted Lyngmo Jan 08 '23 at 09:22
  • Btw, the function will show the base class name, not the name of the derived class. – Ted Lyngmo Jan 08 '23 at 09:37
  • I thought if call it from derived then it should evaluating from base class code and show derived? change consteval to constexpr won't fix problem that I can't use | operator after line where do split(' ') – Vitaly Protasov Jan 08 '23 at 09:44
  • 2
    No, you can [see it here](https://godbolt.org/z/Pe44dYxsd). Changing `consteval` to `constexpr` won't fix _that_ problem, but it will at least fix the declaration error. You could get around the base/derived problem by taking the `source_location` as an argument though [example](https://godbolt.org/z/ean1WT943). Note that MSVC's `function_name()` doesn't even include the class name so it's not very portable – Ted Lyngmo Jan 08 '23 at 10:10
  • Please be more specific about your example, I still don't know what you are trying to do. – 康桓瑋 Jan 08 '23 at 10:13
  • 1
    @康桓瑋 He wants to extract the derived class name from the `source_location` in a base class function. – Ted Lyngmo Jan 08 '23 at 10:14
  • I guess my problem with calling may solve making ClassName() as virtual and use typeid(*this).name() in base instead pretty_function that mean __FUNCTION__ etc, so derived will do job right, but it doesn't help with problems of extract just name through use transform with split together and do it in compile time if possible :) – Vitaly Protasov Jan 08 '23 at 10:25
  • No, making that `constexpr` won't work. A non-portable way (that looks ok in gcc but not MSVC) to make it `consteval` could be [like this](https://godbolt.org/z/dWdhWWGda) – Ted Lyngmo Jan 08 '23 at 13:07

2 Answers2

0

If you need to get name of derived class from base class, then you obviously need virtual function, not static. And to minimize code you need to type just use macro.

Simple way:

#include <iostream>
#include <string_view>

#define DECLARE_GETNAME(X) \
    virtual std::string_view getName() { \
        return #X; \
    }

class Base {
public:
    virtual ~Base() = default;
    DECLARE_GETNAME(Base)
};

class Derived : public Base{
    DECLARE_GETNAME(Derived)
};

int main()
{
    Derived d;
    Base& b = d;
    std::cout << b.getName();
}

If you don't want to type class name every time, you can do it also, but little bit more complicated. Something like that:

#include <iostream>
#include <string_view>

template<typename T>
struct TypeName {
    constexpr static std::string_view fullname_intern() {
        #if defined(__clang__) || defined(__GNUC__)
            return __PRETTY_FUNCTION__;
        #elif defined(_MSC_VER)
            return __FUNCSIG__;
        #else
            #error "Unsupported compiler"
        #endif
    }
    constexpr static std::string_view name() {
        size_t prefix_len = TypeName<void>::fullname_intern().find("void");
        size_t multiple   = TypeName<void>::fullname_intern().size() - TypeName<int>::fullname_intern().size();
        size_t dummy_len  = TypeName<void>::fullname_intern().size() - 4*multiple;
        size_t target_len = (fullname_intern().size() - dummy_len)/multiple;
        std::string_view rv = fullname_intern().substr(prefix_len, target_len);
        if (rv.rfind(' ') == rv.npos)
            return rv;
        return rv.substr(rv.rfind(' ')+1);
    }

    using type = T;
    constexpr static std::string_view value = name();
};

#define DECLARE_GETNAME() \
    virtual std::string_view getName() { \
        return TypeName<std::remove_cvref_t<decltype(*this)>>::value; \
    }

class Base {
public:
    virtual ~Base() = default;
    DECLARE_GETNAME()
};

class Derived : public Base{
    DECLARE_GETNAME()
};

int main()
{
    Derived d;
    Base& b = d;
    std::cout << b.getName();
}

Code to get type name copied from here: https://stackoverflow.com/a/68139582/11680056

sklott
  • 2,634
  • 6
  • 17
0

I though about different approaches, seems this somewhy missed.

here is my own solution (dunno if it works in other compilers than MSVC 20:

virtual const std::string_view& ClassName() const {
    
    const auto&& sub_name = [&](auto&& obj) {
        const std::string_view n(typeid(*obj).name());
        auto p = n.find(" ");
        return p == std::string_view::npos ? n : n.substr(p + 1);
    };
    static const std::string_view name = sub_name(this);
    return name;
}
Vitaly Protasov
  • 161
  • 1
  • 10
  • This will work only on MSVC, GCC and Clang use different, mangled, names for types. I think its possible to create some portable solution for typeid name as well, but it will be similar in complexity to `TypeName` I used in my answer. – sklott Jan 09 '23 at 05:51