c++gccclang

An internal compiler error (ICE) is encountered with GCC, and compiling with Clang also produces confusing error messages


The GCC bug report can be found at: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=121655

This C++ static reflection code can automatically obtain the number and names of member variables in an aggregate class, with its core process divided into three steps:

1.At compile time, calculate the total number of member variables by "gradually attempting to construct the aggregate class".

2.Create a static object of the aggregate class according to the number of members, use structured binding to decompose it into member references, and then obtain the pointer to each member.

3.Pass the member pointer into a function template, obtain the function name using std::source_location in the template scope, and parse the member variable name from it.

#include <string>
#include <iostream>
#include <string_view>
#include <source_location>
#include <type_traits>
#include <tuple>
#include <array>

template<typename T, std::size_t N>
struct reflect_member_handler {};

struct type_placeholder
{
    template<typename E>
    operator E() const;
};

template<typename T, typename... Args>
consteval static std::size_t aggregate_member_counter() 
{
    if constexpr( !requires{ T{ Args{}... }; } )
        return sizeof...(Args) - 1;
    else
        return aggregate_member_counter<T, Args..., type_placeholder>();
}

template<typename T>
consteval std::size_t get_member_count() {
    return aggregate_member_counter<T>();
}

template<auto E>
std::string_view Iget_member_name()
{
    static std::source_location location = std::source_location::current();

    std::string_view func_name = location.function_name();
    std::size_t first = func_name.find("::", func_name.find("(&")) + 2;
    std::size_t last = func_name.find(")", first);

    return func_name.substr(first, last - first);
}

#if 0
    //----------------------- Part 1----------------------------
    template<typename T> 
    struct reflect_member_handler<T, 1> 
    { 
        static auto get_member_names() 
        { 
            static T object; 
            static auto& [v0] = object; 
            std::array<std::string_view, 1> names; 
            names[0] = Iget_member_name<&v0>();  
            return names; 
        } 
    };
    //----------------------------------------------------------
#else
    //----------------------- Part 2----------------------------
    template<typename T> 
    struct reflect_member_handler<T, 1> 
    { 
        static auto get_member_names() 
        { 
            static std::byte memory[sizeof(T)]; 
            static T& dummy_object = *reinterpret_cast<T*>(memory); 
            static auto& [v0] = dummy_object; 
            std::array<std::string_view, 1> names; 
            names[0] = Iget_member_name<&v0>();  
            return names; 
        } 
    };
    //----------------------------------------------------------
#endif

struct test_t { int a; };

int main() 
{
    auto member_names = 
        reflect_member_handler<test_t, get_member_count<test_t>()>::get_member_names();
    std::cout << member_names[0] << '\n';
}

The code for Part 1 can be compiled successfully on GCC x86-64 15.2.0 and Clang x86-64 20.1.0. However, compiling the code for Part 2 will result in an error.

GCC x86-64 15.2.0(https://godbolt.org/z/91W5bGqrq):

<source>:35:18: warning: 'std::string_view Iget_member_name() [with auto E = (& v0)]' used but never defined
   35 | std::string_view Iget_member_name()
      |                  ^~~~~~~~~~~~~~~~
during RTL pass: expand
<source>: In static member function 'static auto reflect_member_handler<T, 1>::get_member_names() [with T = test_t]':
<source>:69:45: internal compiler error: Segmentation fault
   69 |             names[0] = Iget_member_name<&v0>();
      |                        ~~~~~~~~~~~~~~~~~~~~~^~
0x228dd45 diagnostic_context::diagnostic_impl(rich_location*, diagnostic_metadata const*, diagnostic_option_id, char const*, __va_list_tag (*) [1], diagnostic_t)
    ???:0
0x229f296 internal_error(char const*, ...)
    ???:0
0x13561cb make_decl_rtl(tree_node*)
    ???:0
0xaefebc expand_call(tree_node*, rtx_def*, int)
    ???:0
0xc1758e expand_expr_real_1(tree_node*, rtx_def*, machine_mode, expand_modifier, rtx_def**, bool)
    ???:0
0xc22c2e store_expr(tree_node*, rtx_def*, int, bool, bool)
    ???:0
Please submit a full bug report, with preprocessed source (by using -freport-bug).
Please include the complete backtrace with any bug report.
See <https://gcc.gnu.org/bugs/> for instructions.
Compiler returned: 1

Clang x86-64 20.1.0(https://godbolt.org/z/5P119MWzd):

<source>:69:24: error: no matching function for call to 'Iget_member_name'
   69 |             names[0] = Iget_member_name<&v0>();  
      |                        ^~~~~~~~~~~~~~~~~~~~~
<source>:80:69: note: in instantiation of member function 'reflect_member_handler<test_t, 1>::get_member_names' requested here
   80 |         reflect_member_handler<test_t, get_member_count<test_t>()>::get_member_names();
      |                                                                     ^
<source>:35:18: note: candidate template ignored: invalid explicitly-specified argument for template parameter 'E'
   35 | std::string_view Iget_member_name()
      |                  ^
1 error generated.
Compiler returned: 1

This issue does not seem to be a naming-related error. How should I explain this problem?

Some have suggested that the issue might be related to the use of reinterpret_cast, yet the relevant code below can be compiled successfully with GCC x86-64 15.2.0, but fails to compile with Clang(https://godbolt.org/z/WsonEsdv5).

#include <source_location>
#include <iostream>

template<auto E>
void test2() {
    std::cout << std::source_location::current().function_name() <<"\n";
}

template<typename T>
void test1()
{
    static std::byte memory[sizeof(T)];
    static T& dummy_object = *reinterpret_cast<T*>(memory);
    test2<&dummy_object>();
}

int main() 
{
    test1<double>();
}

Do you have any idea in this issue?


Solution

  • According to feedback from the GCC team, the issue that causes an Internal Compiler Error (ICE) in GCC is that GCC fails to correctly handle the address of a global variable which, after structured binding of dummy_object, is used as a template argument.