Макрос, который определяет, был ли передан тип - PullRequest
0 голосов
/ 08 мая 2018

Я хочу сделать свой typeid, используя макросы.Например, если я вызываю MY_TYPEID(token), я хочу, чтобы он разрешил

my_type_info<token>(), если это тип, и

my_type_info(token), если это значение.

IsЕсть ли способ сделать это в C ++?

1 Ответ

0 голосов
/ 11 мая 2018

Это было ... тяжело.

Вот готовый макрос:

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

Эта ... интересная штуковина опирается на тот же базовый принцип, что и этот мой другой ответ : thing является либо ссылкой для пересылки, либо объявлением функции в зависимости от того, является ли параметр выражением или тип.

Случай, когда thing является ссылкой, прост (ed): она просто переходит в качестве перенаправленного параметра в my_type_info, который будет его оттуда получать.

Интересен случай, когда thing - функция: она имеет выведенный тип возвращаемого значения, но не была (и не будет) определена. Таким образом, невозможно использовать его, пока не будет дано определение. Это «использование» включает в себя тривиальное использование, такое как простое thing;: просто попытка вставить его в выражение делает программу плохо сформированной.

Эта характеристика обнаружена через слой SFINAE: probe - это общая лямбда, тип возвращаемого значения которой thing. Но так как он является общим, он на самом деле не взорвется, пока мы не назовем лямбду. Это именно то, что 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 (лямбда). Если thing действительно ожидал определения своего возвращаемого типа, это создание экземпляра завершится неудачно, и вся перегрузка будет удалена SFINAE. Вторая перегрузка не делает этого и всегда действительна, но никогда не имеет приоритета из-за ....

Итак, теперь у нас есть способ через detail_type_info::wizz(probe) определить, является ли аргумент макроса типом (false) или выражением (true). Это включается if constexpr, который становится действительным, делая внешнюю лямбду шаблоном.

Есть одно последнее препятствие: 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), в этом случае компилятор сразу блеет.

Именно поэтому я повторно использовал еще один из моих ответов , где я реализовал my_decltype, то есть decltype для выражений и no-op для типов, таким образом всегда формируя действительный вызов функции.

После установки всего этого механизма и добавления двух заглушек 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__)                        \
            >();                                                \
    }()

Живая демоверсия на Coliru

...