Можно ли выразить static_assert для выражения, которое не должно компилироваться? - PullRequest
8 голосов
/ 16 марта 2019

Я хотел бы выразить static_assert в форме:

static_assert(expression should not compile);

Позвольте мне добавить полный пример:

template <bool Big>
struct A{};

template <>
struct A<true>
{
    void a() {}
};

A<false> b;

static_assert(!compile(b.a()));
or
static_assert(!compile(A<false>::a()));

Итак, идея в том, чтобы быть уверенным, что выражение (с правильным синтаксисом) не скомпилируется.

Будет лучше, если решение использует только C ++ 11, если это возможно.

1 Ответ

1 голос
/ 17 марта 2019

ОК, учитывая, что контекст вашего вопроса несколько расплывчатый, этот ответ может не подходить для вашего случая.Тем не менее, я нашел это очень интересным испытанием.

Очевидно, что, как указано в комментариях, решение должно будет использовать некое (выражение) 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.

Вот полный пример .

...