Comments 6
Даже не знаю что написать в комментарии:) Это реально очень круто, но я потерялся где-то не дойдя до середины, хотя понятно что это очередное нестандартное использование языка. А для рефлексии времени компиляции в большинстве случаев прекрасно подходят старые сишные макросы. А там обычно однотипная задача: описать некоторый список кортежей, который можно использовать для разных целей: объявить из него поля структуры, сделать массив значений, обработать в цикле, загрузить в GUI, сделать сериализацию.
Даже не знаю что написать в комментарии:)
Что пока в стандарте C++ нет чего-то полезного всегда можно попробовать сделать это через Ж дендро-фекальным методом, чтобы благодарные потомки, кому не повезет сопровождать подобный код в будущем, вспоминали автора этих наворотов незлым тихим словом.
Ну вот иногда сишные макросы всё же не подходят. Я писала свой фреймворк с DI и там возникла потребность выявлять зависимости компонентов(Использовался Service Locator с данными нужными в шаблоне).
Если выносить информацию из тела, то это нужно писать бойлерплейт, +меньше диагностируется на КТ, +легко можно ошибиться, а проверка только на рантайме, в сишные макросы это адекватно и не запихнуть.
Пришлось вспоминать про лупхолы и придумывать им такое применение.
Тут затесалась ошибка, делающая даже не UB, а IFNDR.
Во-первых, шаблонная константа kTest инстанцируется в точке первого использования - и далее переиспользуется. Поэтому
static_assert(!kTest<U>);
...
static_assert( kTest<U>);
не прокатит. Либо первый ассерт упадёт, либо второй.
Во-вторых, https://timsong-cpp.github.io/cppwp/temp.constr.constr#temp.constr.atomic-3.sentence-5
Даже если мы будем перезапрашивать выражение у данной шаблонной функции
template<class T, int UniqueID> constexpr bool kTest = requires{.....T....};
static_assert(!kTest<U, __LINE__>);
...
static_assert( kTest<U, __LINE__>);
или чтоб совсем по-хитрому
template<class T, auto F=[]{}> constexpr bool kTest = requires{.....T....};
static_assert(!kTest<U /* , F = some unique lambda */>);
...
static_assert( kTest<U /* , F = some unique lambda */>);
то нарушим указанный пункт стандарта.
Вот можете полюбоваться
Ещё замечание.
Во-первых, у конструктора (конструкторов) может быть более одной перегрузки. Поэтому Reflect<T> по-хорошему должен был бы отдавать не один кортеж типов, а набор кортежей.
Во-вторых, если конструктор содержит шаблонные аргументы, то Caster сработает криво. Либо конструктор примет его как родного, либо не примет вообще, потому что это будет считаться двойным неявным приведением.
В-третьих, если конструктор принимает initializer_list, - то получится странное
struct X { X(std::initializer_list<int>); };
struct Y { Y(char, std::initializer_list<int>); };
// caster
struct C { template<class T> opertor T() const; };
requires{ X{}; } // якобы 0-арный
requires{ X{{C{}}}; } // int - чтобы узнать тип элемента списка
requires{ Y{C{}, C{}}; } // char, initializer_list<int> - тут всё просто
В-четвёртых, явно объявленные стандартные конструкторы - копирования/перемещения - добавят угару. Даже если они =default.
В общем, техника имеет ряд ограничений, либо её надо допиливать и тестировать на всём безумном сочетании условий.
Type Loopholes: решая нерешаемое. Рефлексия времени компиляции