So I was recently exposed to the grotesqueness that is the so-called "abominable function type" in C (originates from this paper as far as I know: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0172r0.html). I thought about it for a while and it does seem to make some amount of sense, but everything would be much cleaner and more satisfying if one were to somehow remove them from the language completely.
Maybe there are other points of origin, but (for me at least) most issues with abominable function types come from handling member functions. If I want to get the type of a member function, I do something like this:
template <typename T>
struct remove_ptr_to_member { using type = T; };
template <typename T, typename class_t>
struct remove_ptr_to_member<T class_t::*> { using type = T; };
struct container {
void func() const;
};
using member_function_type = typename remove_ptr_to_member<decltype(container::func)>::type;
Since there exists a const
in the declaration of func()
, removing the pointer-to-member part leaves us with an abominable function.
This could be avoided if one were to put the invisible this
argument into the type of member functions. Then, the things that would normally cause an abominable function would all be applied to the this
argument (const
would mean a const this
pointer, &&
would mean an rvalue reference to an instance of this
, etc...).
Now, even if you remove the pointer-to-member part of the type, all that you're left with is a totally normal function type. The great thing about this would be that it would cut down on the amount of boiler-plate you have to write when implementing things like is_function
.
Classic method:
// primary template
template<class>
struct is_function : std::false_type { };
// specialization for regular functions
template<class Ret, class... Args>
struct is_function<Ret(Args...)> : std::true_type {};
// specialization for variadic functions such as std::printf
template<class Ret, class... Args>
struct is_function<Ret(Args......)> : std::true_type {};
// specialization for function types that have cv-qualifiers
template<class Ret, class... Args>
struct is_function<Ret(Args...) const> : std::true_type {};
template<class Ret, class... Args>
struct is_function<Ret(Args...) volatile> : std::true_type {};
template<class Ret, class... Args>
struct is_function<Ret(Args...) const volatile> : std::true_type {};
// etc... (goes on for a while)
New method:
// primary template
template <typename>
struct is_function : std::false_type { };
// specialization for regular functions
template<typename Ret, typname... Args>
struct is_function<Ret(Args...)> : std::true_type { };
// specialization for variadic functions such as std::printf
template<typename Ret, typname... Args>
struct is_function<Ret(Args......)> : std::true_type { };
// same thing but with noexcept
template<typename Ret, typname... Args>
struct is_function<Ret(Args...) noexcept> : std::true_type { };
template<typename Ret, typname... Args>
struct is_function<Ret(Args......) noexcept> : std::true_type { };
And that would be it. No long boiler-plate required, since all the intricasies (const
, volatile
, etc...) are hidden within the first argument.
I'm sorry for waiting so long before asking the actual question, but the time has come: Does anyone have a counter-argument to this fix? Is it even a fix, maybe it doesn't deal with everything there is to deal with? I guess what I'm wondering is, why this isn't in the standard? If it's such a great idea, someone must have thought of it right? There has got to be some extra information that I haven't considered, because this seems too simple.
CodePudding user response:
Pretty much exactly what you are suggesting has already been accepted for C 23 as member functions with explicit object parameters like this:
struct container {
void func(this container const&);
};
The type of this function is then void(container const&)
instead of void() const
or equivalently the type of &container::func
will be void(*)(container const&)
instead of void (container::*)() const
.
See P0847 which introduced this new feature.
But that is not going to change that the old-style non-static member functions with implicit instead of explicit object parameter need to have the cvref-qualified function types. Changing the type of these functions would break a lot of code, so it is too late for that.