I want to make my own typeid
using macros. For example If I call MY_TYPEID(token)
I want it it resolve to
my_type_info<token>()
if it's a type, and
my_type_info(token)
if it's a value.
Is there any way to do this in C++?
I want to make my own typeid
using macros. For example If I call MY_TYPEID(token)
I want it it resolve to
my_type_info<token>()
if it's a type, and
my_type_info(token)
if it's a value.
Is there any way to do this in C++?
This was... tough.
Here is the finished macro:
#define MY_TYPEID(...) \
[&](auto...) { \
\
auto &&thing(__VA_ARGS__); \
auto probe = [](auto...) -> decltype(thing, void()) {}; \
\
if constexpr(detail_type_info::wizz(probe)) \
return detail_type_info::my_type_info( \
std::forward<decltype(thing)>(thing) \
); \
else \
return detail_type_info::my_type_info< \
my_decltype(__VA_ARGS__) \
>(); \
}()
This... interesting-looking gizmo relies on the same basic principle as this other answer of mine: thing
is either a forwarding reference or a function declaration depending on whether the parameter is an expression or a type.
The case where thing
is a reference is straightforward(ed): it will just end up as a forwarded parameter to my_type_info
which will pick it up from there.
The case where thing
is a function is interesting: it has a deduced return type but hasn't been (and will not be) defined. It is thus impossible to use it until a definition has been provided. This "use" includes trivial use, such as a plain thing;
: just trying to put it into an expression makes the program ill-formed.
This characteristic is detected through a layer of SFINAE: probe
is a generic lambda whose return type uses thing
. But since it is generic, this does not actually blow up until we call the lambda. This is exactly what detail_type_info::wizz
tries to do:
namespace detail_type_info {
template <class F>
constexpr auto wizz(F probe) -> decltype(probe(), true) { return true; }
constexpr auto wizz(... ) -> decltype( false) { return false; }
}
detail_type_info::wizz(probe)
tries to match one of these overloads. The first overload tries to call probe
in an unevaluated context, instantiating probe
(the lambda)'s call operator. If thing
was, indeed, awaiting to deduce its return type, this instantiation fails and the whole overload is SFINAE'd away. The second overload does no such thing, and is always valid but never prioritized due to ...
.
So we now have a way to tell, through detail_type_info::wizz(probe)
, whether the macro's argument is a type (false
) or an expression (true
). This is switched upon by an if constexpr
, which is made valid by making the outermost lambda a template.
There's one last hurdle: detail_type_info::my_type_info(std::forward<decltype(thing)>(thing))
in the true
branch is always valid (even though it would break if instanciated in the case of thing
being a function declaration).
The false
branch, however, cannot be called in the naïve way as return detail_type_info::my_type_info<__VA_ARGS__>()
, because that can turns into nonsense when __VA_ARGS__
is an expression which is not a valid non-type template parameter (such as a double
), in which case the compiler bleats immediately.
That's why I reused another of my answers where I implemented my_decltype
, which is decltype
on expressions and a no-op on types, thus always forming a valid function call.
With all of this machinery in place, and the addition of two my_type_info
stubs, the following:
namespace detail_type_info {
template <class T>
void my_type_info(T &&) {
std::cout << __PRETTY_FUNCTION__ << '\n';
}
template <class T>
void my_type_info() {
std::cout << __PRETTY_FUNCTION__ << '\n';
}
}
int main() {
MY_TYPEID(int);
MY_TYPEID(4.2);
}
... outputs as expected:
void detail_type_info::my_type_info() [with T = int]
void detail_type_info::my_type_info(T&&) [with T = double]
Full code:
namespace detail_typeOrName {
struct probe {
template <class T>
operator T() const;
};
template <class T>
T operator * (T const &, probe);
probe operator *(probe);
}
#define my_decltype(x) decltype((x) * detail_typeOrName::probe{})
namespace detail_type_info {
template <class T>
void my_type_info(T &&) {
std::cout << __PRETTY_FUNCTION__ << '\n';
}
template <class T>
void my_type_info() {
std::cout << __PRETTY_FUNCTION__ << '\n';
}
template <class F>
constexpr auto wizz(F probe) -> decltype(probe(), true) { return true; }
constexpr auto wizz(... ) -> decltype( false) { return false; }
}
#define MY_TYPEID(...) \
[&](auto...) { \
\
auto &&thing(__VA_ARGS__); \
auto probe = [](auto...) -> decltype(thing, void()) {}; \
\
if constexpr(detail_type_info::wizz(probe)) \
return detail_type_info::my_type_info( \
std::forward<decltype(thing)>(thing) \
); \
else \
return detail_type_info::my_type_info< \
my_decltype(__VA_ARGS__) \
>(); \
}()