in c++, function types have a "language linkage" associated with them: this is either "C++", "C", or some other implementation-defined language. and the standard very explicitly states that Two function types with different language linkages are distinct types even if they are otherwise identical.
the idea is that some implementations may have different calling conventions for c++ functions than for c functions, so they can't be intermixed
gcc and clang however, just, don't store language linkage information with the type. so two functions with different language linkages can be identical:
#include <type_traits>
extern "C" using c_func = void ();
// this static assertion should fail, but it doesn't
static_assert(std::is_same<c_func, void ()>::value);
this can also cause erroneous compilation failures when overloading a function which takes in a function pointer parameter:
extern "C" using c_func = void ();
void f(c_func *) {}
void f(void (*)()) {}
the above code should compile, but gcc and clang both think that the parameter lists are identical and thus this violates the one definition rule
IMO the blame here doesn't lie on gcc or clang; it lies on the standard. that is, the standard is wrong and should be updated to make this implementation-defined. gcc and clang can't change their behavior because that would be a breaking ABI change (extern "C" function types in mangled names would be encoded differently). and they have no reason to make this change: the calling conventions for c and c++ are identical on, as far as i can tell, pretty much every platform