确定类型是否通过的宏

时间:2018-05-08 20:45:17

标签: c++ macros typeid

我想使用宏创建自己的typeid。例如,如果我致电MY_TYPEID(token),我希望它解析为

my_type_info<token>()如果是类型,

my_type_info(token)如果它是一个值。

有没有办法在C ++中做到这一点?

1 个答案:

答案 0 :(得分:2)

这很......很难。

这是完成的宏:

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

这个有趣的Gizmo依赖于与this other answer of mine相同的基本原则:thing是转发引用或函数声明,具体取决于参数是表达式还是类型。

thing作为参考的情况很简单(ed):它最终将作为转发参数传递给my_type_info,这将从那里取出。

thing是一个函数的情况很有意思:它有一个推导的返回类型,但尚未定义(也不会定义)。因此,在提供定义之前不可能使用它。这个“使用”包括普通的使用,例如普通的thing;:只是试图将它放入表达式会使程序格式不正确。

通过一层SFINAE检测到此特征:probe是一个通用lambda,其返回类型使用thing。但由于它是通用的,在我们调用 lambda之前,这实际上并没有爆发。这正是detail_type_info::wizz尝试做的事情:

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)尝试匹配其中一个重载。第一个重载尝试在未评估的上下文中调用probe,实例化probe(lambda)的调用操作符。如果thing确实等待推断其返回类型,则此实例化失败并且整个重载都是SFINAE。第二个重载没有这样的事情,并且总是有效但从未因...而优先考虑。

因此我们现在可以通过detail_type_info::wizz(probe)告诉我们宏的参数是类型(false)还是表达式(true)。这由if constexpr启用,通过使最外面的lambda成为模板而使其有效。

最后一个障碍:detail_type_info::my_type_info(std::forward<decltype(thing)>(thing))分支中的true始终有效(即使在thing作为函数声明的情况下实例化时会中断)。

然而,false分支不能以return detail_type_info::my_type_info<__VA_ARGS__>()的天真方式调用,因为当__VA_ARGS__是一个非有效的非类型表达式时,这可能会变成无意义模板参数(例如double),在这种情况下编译器会立即生效。

这就是为什么我重用another of my answers我实现my_decltype的地方,decltype表达式和无操作类型,因此总是形成一个有效的函数调用。

有了所有这些机制,并添加了两个my_type_info存根,以下内容:

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);
}

...按预期输出:

void detail_type_info::my_type_info() [with T = int]
void detail_type_info::my_type_info(T&&) [with T = double]

完整代码:

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