Это было ... тяжело.
Вот готовый макрос:
#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