ОК, учитывая, что контекст вашего вопроса несколько расплывчатый, этот ответ может не подходить для вашего случая.Тем не менее, я нашел это очень интересным испытанием.
Очевидно, что, как указано в комментариях, решение должно будет использовать некое (выражение) SFINAE.По сути, нам нужен более общий вариант идиомы обнаружения.Однако здесь есть в основном две проблемы:
1) Чтобы запустить SFINAE, нам нужны какие-то шаблоны.
2) Чтобы обеспечить синтаксис compile(XXX)
, нам нужно создатьэти шаблоны "на лету" внутри макроса.В противном случае мы должны были бы заранее определить функцию теста для каждого теста.
Второе ограничение усложняет задачу.Мы можем определить структуры и функции локально внутри лямбды.К сожалению, шаблоны там не разрешены.
Так вот, насколько я смог получить (не на 100% то, что вы хотите, а относительно близко).
Обычно выражение-SFINAE-детекторы используют перегрузку функции (шаблона) или специализацию шаблона класса.Поскольку оба не допускаются внутри лямбды, нам нужен дополнительный слой: функтор, который берет кучу лямбд и вызывает тот, который наилучшим образом соответствует аргументам вызова.Это часто используется в сочетании с std::variant
.
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
Теперь мы можем создать детектор, например:
auto detector = overloaded{
[](auto, auto) -> std::false_type {return {};}
,
[](auto x, int)-> decltype(decltype(x)::a(), std::true_type{}){ return {};}
};
static_assert(!detector(A<false>{}, int{}));
static_assert(detector(A<true>{}, int{}));
Теперь мы можем определить макрос, который определяет и вызываетдектор для нужного выражения:
#define compile(obj, xpr) \
[]() { \
auto check = \
overloaded{[](auto&&, auto) -> std::false_type { return {}; }, \
[](auto&& x, int) -> decltype(x xpr, std::true_type{}) { \
return {}; \
}}; \
return decltype(check(obj, int{})){}; \
}()
Этот макрос создает лямбду, подставляет xpr
в детектор и выполняет вывод типа на decltype(x)
, чтобы заставить SFINAE срабатывать. Его можно использовать какследует:
static_assert(!compile(b, .a()));
static_assert(compile(a, .a()));
int x = 0;
static_assert(compile(x, *= 5));
static_assert(!compile(x, *= "blah"));
К сожалению, он не будет работать с именем типа в качестве первого аргумента.Поэтому нам нужен второй макрос для таких тестов:
#define compile_static(clazz, xpr) \
[]() { \
auto check = overloaded{ \
[](auto, auto) -> std::false_type { return {}; }, \
[](auto x, int) -> decltype(decltype(x) xpr, std::true_type{}) { \
return {}; \
}}; \
return decltype(check(std::declval<clazz>(), int{})){}; \
}()
static_assert(!compile_static(A<false>, ::a()));
static_assert(compile_static(A<true>, ::a()));
Как указано выше, это не 100% того, что вы запрашивали, поскольку нам всегда понадобится дополнительный ,
для разделения аргументов макроса,Также нужны два отдельных макроса.Может быть, это то, что может быть изменено с помощью препроцессора, чтобы определить, начинается ли аргумент xpr
с ::
.И, конечно, могут быть случаи, когда это не работает.но, возможно, это отправная точка.
Требуется c ++ 17, но, похоже, он работает с gcc> = 7, clang> = 5 и даже msvc 19.
Вот полный пример .