Как посылать между assert () и static_assert (), зависимости, если в контексте constexpr? - PullRequest
8 голосов
/ 23 января 2020

В C ++ 11 функциях constexpr второе утверждение, такое как assert(), невозможно. A static_assert() в порядке, но не будет работать, если функция вызывается как «нормальная» функция. Оператор запятой может прийти на помощь. assert(), но уродливо, и некоторые инструменты выкладывают предупреждения об этом.

Рассмотрим такой «получатель», который идеально подходит для утверждения. Но я хотел бы сохранить какое-то утверждение для времени выполнения и времени компиляции, но не могу просто перегрузить в зависимости от контекста «constexpr».

template<int Size>
struct Array {
  int m_vals[Size];
  constexpr const int& getElement( int idx ) const
  {
    ASSERT( idx < Size ); // a no-go for constexpr funcs in c++11
    // not possible, even in constexpr calls as being pointed out, but what I would like:
    static_assert( idx < Size, "out-of-bounds" );
    return m_vals[idx];
  }
};

Побочные условия: C ++ 11, без кучи, без исключений , никаких особенностей компилятора.

Примечание , как указали комментаторы (спасибо!), static_assert в аргументе невозможен (но было бы неплохо). В этой ситуации компилятор выдал другую ошибку при доступе за пределы.

Ответы [ 5 ]

3 голосов
/ 23 января 2020

Лучше, чем выражение с запятой, вы можете использовать троичное условное выражение. Первый операнд - это ваш предикат утверждения, второй операнд - это ваше выражение успеха, и, поскольку третий операнд может быть любым выражением, даже не используемым в контексте с константой C ++ 11, вы можете использовать лямбду для вызова * 1001 вашей библиотеки. * Facility:

#define ASSERT_EXPR(pred, success)    \
    ((pred) ?                         \
     (success) :                      \
     [&]() -> decltype((success))     \
     {                                \
         ASSERT(false && (pred));     \
         struct nxg { nxg() {} } nxg; \
         return (success);            \
     }())

Объяснение тела лямбды:

  • ASSERT(false && (pred)), чтобы убедиться, что ваш механизм утверждения вызывается с соответствующим выражением (для строкового преобразования).
  • struct nxg { nxg() {} } nxg предназначен для обеспечения безопасности в будущем, чтобы гарантировать, что если вы компилируете в C ++ 17 или выше с NDEBUG, лямбда все еще не является constexpr, и поэтому утверждение принудительно применяется в контекст оценки.
  • return (success) существует по двум причинам: для обеспечения того, чтобы второй и третий операнды имели одинаковый тип, и чтобы, если ваша библиотека учитывает NDEBUG, выражение success возвращается независимо от того, pred. (pred будет оценено , но вы надеетесь, что предикаты утверждения должны быть дешевыми для оценки и не иметь побочных эффектов.)

Пример использования:

template<int Size>
struct Array {
  int m_vals[Size];
  constexpr int getElement( int idx ) const
  {
    return ASSERT_EXPR(idx < Size, m_vals[idx]);
  }
};

constexpr int I = Array<2>{1, 2}.getElement(1); // OK
constexpr int J = Array<2>{1, 2}.getElement(3); // fails
2 голосов
/ 23 января 2020

static_assert здесь нельзя использовать. Аргумент функции constexpr недопустим в константном выражении. Следовательно, нет решения вашей проблемы при данных ограничениях.

Однако мы можем решить проблему, согнув два ограничения

  1. не использовать static_assert (вместо этого использовать другие методы для получения диагностики во время компиляции c), а

  2. не обращать внимания на то, что оператор запятой "некрасив, и некоторые инструменты выкладывают предупреждения об этом «. (Показывать его уродство является печальным следствием строгих требований функций C ++ 11 constexpr)

Тогда мы можем использовать обычный assert:

template <int Size>
struct Array {
  int m_vals[Size];
  constexpr const int& getElement(int idx) const
  {
    return assert(idx < Size), m_vals[idx];
  }
};

В постоянном контексте оценки это вызовет ошибку компилятора, такую ​​как error: call to non-'constexpr' function 'void __assert_fail(const char*, const char*, unsigned int, const char*)'.

2 голосов
/ 23 января 2020

Что-то вроде

void assert_impl() { assert(false); } // Replace body with own implementation

#ifdef NDEBUG // Replace with own conditional
#define my_assert(condition) ((void)0)
#else
#define my_assert(condition) ((condition) ? (void()) : (assert_impl(), void()))
#endif

template<int Size>
struct Array {
  int m_vals[Size];
  constexpr const int& getElement( int idx ) const
  {
    return my_assert(idx < Size), m_vals[idx];
  }
};

Это даст ошибку времени компиляции при ошибке подтверждения при использовании в контексте , требующем константного выражения (потому что это вызовет не-constexpr function).

Иначе во время выполнения произойдет сбой при вызове assert (или вашего аналога).

Это лучшее, что вы можете сделать, насколько я знаю. Невозможно использовать значение idx для принудительной проверки во время компиляции вне контекста , требующего константных выражений.

Синтаксис оператора запятой не очень приятный, но C + +11 constexpr функции очень ограничены.

Конечно, как вы уже отметили, неопределенное поведение будет диагностироваться в любом случае, если функция используется в контексте, требующем постоянного выражения.

Если вы знаете, что assert (или ваш аналог) не распространяется на что-либо, что запрещено в константном выражении, если условие оценивается как true, но делает это, если оно оценивается как false, тогда вы можете использовать его непосредственно вместо my_assert и пропустите косвенное указание, которое я строю в своем коде.

0 голосов
/ 23 января 2020

Это то, что мне подходит для большинства компиляторов: https://godbolt.org/z/4nT2ub

template<int Size>
struct Array {
  int m_vals[Size];
  constexpr const int& getElement(int idx) const
  {
    assert(idx < Size);
    return m_vals[idx];
  }
};

Теперь static_assert устарел, поскольку constexpr не может содержать неопределенное поведение. Поэтому, когда вы перебираете компилятор индекса массива, выдает правильную ошибку. См. Здесь .

Проблема assert. Это макрос, реализация которого не определена. Если компилятор использует функцию, которая не является constexpr, он потерпит неудачу, но, как вы можете видеть, 3 основных компилятора не имеют проблем с этим.

0 голосов
/ 23 января 2020

c ++ 11 не может быть таким ... либо idx константа, либо нет
было бы неплохо, если бы по одной функции каждая
Это могло бы быть вроде этого, если принудительно использовать одну функцию

template<int Size>
struct Array {
    int m_vals[Size];
    constexpr const  int& getElement( int idx ) const       
    {
    if constexpr(is_same_v<decltype(idx), const int>)
        static_assert( idx < Size, "out-of-bounds" ); // a no-go for non-constexpr calls
    else 
        assert( idx < Size ); // a no-go for constexpr funcs in c++11

    return m_vals[idx];
    }
};

int main() {                // E.g. up to next //

        Array<7> M;  
        int i=M.getElement(1);
}                           //
...