0

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++?

andreasxp
  • 174
  • 2
  • 9
  • 1
    Why do you want to do that? –  May 08 '18 at 20:50
  • As @Neil Butterworth said, why? Secondly, *why* use macros? Macros should only be used as a last resort when you have no other options (meaning; *very* rarely). – Jesper Juhl May 08 '18 at 20:52
  • 4
    The preprocessor doesn't know anything about how tokens are defined in the language. That phase of processing occurs after the preprocessor does all its replacements. – Barmar May 08 '18 at 20:53
  • 1
    It might well be possible, though I advise against using macros, and would only recommend doing this as a learning exercise. Take a look into SFINAE, which lets you implement compile-time conditional code generation on template parameters. – alter_igel May 08 '18 at 21:00
  • @Neil Butterworth It looks nice, and more importantly, would behave like real typeid does (which works regardless of what is passed in). Right now I just use my_type_info with round/angle brackets, but if this macro hack is possible, I would at least want to know about it. – andreasxp May 09 '18 at 09:06

1 Answers1

2

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__)                        \
            >();                                                \
    }()

Live demo on Coliru

Quentin
  • 62,093
  • 7
  • 131
  • 191
  • This is amazing! Thank you. Could you clarify something about my_decltype for me please? You said it is needed because `__VA_ARGS__` could be an expression which is an invalid non-type template param. But I thought all expressions were already filtered out by `if constexpr` earlier? – andreasxp May 13 '18 at 14:05
  • @andreasxp because the discarded branch still has to be syntactically valid, and such a parameter is invalid in all cases. – Quentin May 13 '18 at 14:45