c++classname

Why this __METHOD__NAME__ requires a memory copy?


The idea is from https://stackoverflow.com/a/15775519/1277762

I added a '\0' at end of string_view so we can use data() for printf, spdlog, et al.

I use this macro to print function name with class name.

However, I find that the compiler is not smart enough to inline the string, but requires a memory copy to stack first:

https://godbolt.org/z/bqob37G3z

See the difference between CALL and CALLFUNC in main function.

Is it possible to tell compiler just put the string in some RO section, like const char *?

template<std::size_t N>
consteval const std::array<char, N> __get_function_name(const char * arr)
{
    std::array<char, N>         data {};
    std::string_view            prettyFunction(arr);
    size_t                      bracket = prettyFunction.rfind("(");
    size_t                      space = prettyFunction.rfind(" ", bracket) + 1;
    size_t                      i;

    for (i = 0; i < bracket - space; i += 1) {
        data[i] = arr[space + i];
    }
    data[i] = '\0';

    return data;
}

#define __METHOD_NAME__ __get_function_name<strlen(__PRETTY_FUNCTION__)>(__PRETTY_FUNCTION__).data()

Thanks @n.m. and user17732522. I finally get a workable version: https://godbolt.org/z/sof1j3Md4

Still not perfect as this solution needs search '(' and ' ' twice, which might increase compile time.


Updated: only call rfind(" ") once: https://godbolt.org/z/zYcajqaje

#include <array>
#include <string_view>

constexpr size_t my_func_end(const char * arr)
{
    std::string_view prettyFunction(arr);
    return prettyFunction.rfind("(");
}

constexpr size_t my_func_start(const char * arr, const size_t end)
{
    std::string_view prettyFunction(arr);
    return prettyFunction.rfind(" ", end) + 1;
}


template<std::size_t S, std::size_t E>
constexpr const std::array<char, E - S + 1> my_get_function_name(const char * arr)
{
    std::array<char, E - S + 1> data {};
    size_t i;
    for ( i = 0; i < E - S; i += 1) {
        data[i] = arr[S + i];
    }
    data[i] = '\0';

    return data;
}

template<auto tofix>
struct fixme
{
   static constexpr decltype(tofix) fixed = tofix;
};

#define __METHOD_NAME_ARRAY__(x)    my_get_function_name<my_func_start(x, my_func_end(x)), my_func_end(x)>(x)
#define __METHOD_NAME__             (fixme<__METHOD_NAME_ARRAY__(__PRETTY_FUNCTION__)>::fixed.data())

Not sure if compiler can cache the calculation. If yes, it is good enough.


Solution

  • The compiler doesn't realize or take into account the specific behavior of puts, namely that it doesn't store the pointer it is passed and doesn't call its caller again.

    The problem is that without this knowledge the compiler has to take into account the possibility that puts will store the pointer it is passed in e.g. a global variable, then calls its caller again, and then compares the new pointer argument with the old one stored in the global variable. These must compare unequal because they are pointers into different temporary objects, both in their lifetime. So the compiler can't reuse the same read-only static memory location as the argument to the puts call.

    So you need to tell the compiler explicitly to use a static memory location:

    #define METHOD_NAME get_function_name<my_strlen(__PRETTY_FUNCTION__)>(__PRETTY_FUNCTION__)
    
    #define CALL() { static constexpr auto v = METHOD_NAME; puts(v.data()); }
    

    Technically the constexpr on v is redundant with consteval on the function. Just constexpr on both would also be sufficient.

    If you don't add constexpr on v you might want to add const though to make sure that the compiler won't need to consider the possibility that puts will modify the contents of the string, although it seems that GCC in your example is aware of that. (That puts takes a const char* as argument is not sufficient to establish this.)


    You can't use strlen there by the way (assuming you want this to be portable to some other compiler beyond GCC that is supporting __PRETTY_FUNCTION__ in the way you are using it, e.g. Clang with libc++). That GCC is allowing it without diagnostic is not standard-conforming and it is not guaranteed to work on other compilers. std::strlen is not marked constexpr per standard. You can use std::char_traits<char>::length instead, which is constexpr since C++17.