Подтвердить что ты не робот

Constexpr, static_assert и inlining

Я ранее спрашивал о перегрузке функции на основе аргументов constexpr. Я пытаюсь обойти неутешительный ответ на этот вопрос, чтобы сделать более умную функцию утверждения. Это примерно то, что я пытаюсь сделать:

inline void smart_assert (bool condition) {
    if (is_constexpr (condition))
        static_assert (condition, "Error!!!");
    else
        assert (condition);
}

В принципе, идея состоит в том, что проверка времени компиляции всегда лучше проверки времени выполнения, если ее можно проверить во время компиляции. Однако из-за таких вещей, как inlining и постоянное складывание, я не всегда знаю, возможна ли проверка времени компиляции. Это означает, что могут быть случаи, когда assert (condition) скомпилируется до assert(false), и код просто ждет, когда я запустим его и выполнит этот путь до того, как я узнаю, что есть ошибка.

Следовательно, если бы был какой-то способ проверить, является ли условие constexpr (из-за вложения или других оптимизаций), я мог бы по возможности называть static_assert и отказываться от времени выполнения в противном случае. К счастью, gcc имеет встроенный __builtin_constant_p (exp), который возвращает true, если exp является constexpr. Я не знаю, есть ли у других компиляторов это внутреннее, но я надеялся, что это решит мою проблему. Это код, который я придумал:

#include <cassert>
#undef IS_CONSTEXPR

#if defined __GNUC__
    #define IS_CONSTEXPR(exp) __builtin_constant_p (exp)
#else
    #define IS_CONSTEXPR(exp) false
#endif
// TODO: Add other compilers

inline void smart_assert (bool const condition) { 
    static_assert (!IS_CONSTEXPR(condition) or condition, "Error!!!");
    if (!IS_CONSTEXPR(condition))
        assert (condition);
}

#undef IS_CONSTEXPR

static_assert полагается на поведение короткого замыкания or. Если IS_CONSTEXPR истинно, тогда можно использовать static_assert, а условие !true or condition, что совпадает с просто condition. Если IS_CONSTEXPR является ложным, то static_assert не может использоваться, и условие !false or condition, которое равно как и true, а static_assert игнорируется. Если параметр static_assert не может быть проверен, потому что condition не является constexpr, я добавляю во время выполнения assert в мой код как последнее действие. Однако это не работает, благодаря неспособности использовать аргументы функции в static_assert, даже если аргументы constexpr.

В частности, это то, что происходит, если я пытаюсь скомпилировать с помощью gcc:

// main.cpp
int main () {
    smart_assert (false);
    return 0;
}

g++ main.cpp -std=c++0x -O0

Все нормально, компилируется нормально. Нет никакой вставки без оптимизации, поэтому IS_CONSTEXPR является ложным, а static_assert игнорируется, поэтому я просто получаю инструкцию assert (которая не работает). Тем не менее,

[[email protected] test]$ g++ main.cpp -std=c++0x -O1
In file included from main.cpp:1:0:
smart_assert.hpp: In function ‘void smart_assert(bool)’:
smart_assert.hpp:12:3: error: non-constant condition for static assertion
smart_assert.hpp:12:3: error: ‘condition’ is not a constant expression

Как только я включу какие-либо оптимизации и, возможно, разрешу запуск static_assert, он терпит неудачу, потому что я не могу использовать аргументы функции в static_assert. Есть ли способ обойти это (даже если это означает реализацию моего собственного static_assert)? Я чувствую, что мои проекты на С++ теоретически могут принести пользу от более умного утверждения assert, который как можно раньше обнаруживает ошибки.

Кажется, что создание smart_assert функционального макроса решит проблему в общем случае. Очевидно, что это будет работать в этом простом примере, но condition, возможно, исходит из функции на два уровня вверх по графику вызовов (но все равно становится известен компилятору как constexpr из-за вложения), который работает в том же проблема использования параметра функции в static_assert.

4b9b3361

Ответ 1

Это поможет вам начать

template<typename T> 
constexpr typename remove_reference<T>::type makeprval(T && t) {
  return t;
}

#define isprvalconstexpr(e) noexcept(makeprval(e))

Ответ 2

Явный хороший, неявный плохой, в общем.

Программист всегда может попробовать static_assert.

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

Вы можете упростить это, предоставив общую форму, чтобы изменение сводилось к, например, STATIC_ASSERT( x+x == 4 )DYNAMIC_ASSERT( x+x == 4 ), просто переименование.

Тем не менее, поскольку в вашем случае вам нужна только оптимизация времени программиста, если эта оптимизация доступна, т.е. поскольку вы, предположительно, не хотите получать одинаковые результаты всегда со всеми компиляторами, вы всегда можете попробовать что-то как & hellip;

#include <iostream>
using namespace std;

void foo( void const* ) { cout << "compile time constant" << endl; }
void foo( ... ) { cout << "hm, run time,,," << endl; }

#define CHECK( e ) cout << #e << " is "; foo( long((e)-(e)) )

int main()
{
    int x   = 2134;
    int const y     = 2134;

    CHECK( x );
    CHECK( y );
}

Если вы это сделаете, то, пожалуйста, сообщите нам, как это произошло.

Примечание: приведенный выше код дает разные результаты с MSVC 10.0 и g++ 4.6.


Обновление: я задавался вопросом, как комментарий о том, как работает код выше, получил так много upvotes. Я подумал, может быть, он говорит то, чего я просто не понимаю. Поэтому я приступил к работе OP, проверив, как эта идея была достигнута.

В этот момент я думаю, что если функцию constexpr можно использовать для работы с g++, тогда можно решить проблему также и для g++, иначе, только для других компиляторов.

Выше это до тех пор, пока я получил поддержку g++. Это хорошо работает (решает проблему OP) для Visual С++, используя идею, которую я представил. Но не с g++:

#include <assert.h>
#include <iostream>
using namespace std;

#ifdef __GNUC__
    namespace detail {
        typedef double (&Yes)[1];
        typedef double (&No)[2];

        template< unsigned n >
        Yes foo( char const (&)[n] );

        No foo( ... );
    }  // namespace detail

    #define CASSERT( e )                                        \
        do {                                                    \
            char a[1 + ((e)-(e))];                              \
            enum { isConstExpr = sizeof( detail::foo( a ) ) == sizeof( detail::Yes ) }; \
            cout << "isConstExpr = " << boolalpha << !!isConstExpr << endl; \
            (void)(isConstExpr? 1/!!(e) : (assert( e ), 0));    \
        } while( false )
#else
    namespace detail {
        struct IsConstExpr
        {
            typedef double (&YesType)[1];
            typedef double (&NoType)[2];

            static YesType check( void const* );
            static NoType check( ... );
        };
    }  // namespace detail

    #define CASSERT( e )                                            \
        do {                                                        \
            enum { isConstExpr =                                    \
                (sizeof( detail::IsConstExpr::check( e - e ) ) ==   \
                    sizeof( detail::IsConstExpr::YesType )) };      \
            (void)(isConstExpr? 1/!!(e) : (assert( e ), 0));        \
        } while( false )
#endif

int main()
{
#if defined( STATIC_TRUE )
    enum { x = true };
    CASSERT( x );
    cout << "This should be displayed, OK." << endl;
#elif defined( STATIC_FALSE )
    enum { x = false };
    CASSERT( x );
    cerr << "!This should not even have compiled." << endl;
#elif defined( DYNAMIC_TRUE )
    bool x = true;
    CASSERT( x );
    cout << "This should be displayed, OK." << endl;
#elif defined( DYNAMIC_FALSE )
    bool x = false;
    CASSERT( x );
    cout << "!Should already have asserted." << endl;
#else
    #error "Hey, u must define a test case symbol."
#endif
}

Пример проблемы с g++:

[D:\dev\test]
> g++ foo.cpp -Werror=div-by-zero -D DYNAMIC_FALSE

[D:\dev\test]
> a
isConstExpr = true
!Should already have asserted.

[D:\dev\test]
> _

То есть, g++-отчеты (даже с помощью встроенной функции и даже для создания VLA или нет), что переменная non const, которая знает значение, является постоянной, но тогда она не может применять это знание для целочисленное деление, чтобы оно не выдавало предупреждения.

Argh.


Обновление 2. Ну, я тупой: конечно, макрос может просто добавить обычный assert, чтобы иметь там в любом случае. Поскольку OP интересуется только тем, что статические утверждают, когда они доступны, что не для g++ в некоторых случаях. Проблема решена и была решена изначально.

Ответ 3

Как ответ на другой вопрос разочаровывает? Он практически точно описывает то, что вы сейчас описываете, за исключением того, как компилятор печатает текст в диагностическом сообщении.

Причина, по которой это необходимо сделать с throw, заключается в том, что оценка времени constexpr во время компиляции в контексте, который может быть оценена во время выполнения, является необязательной. Например, реализация может позволить вам перейти через код constexpr в режиме отладки.

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

Как для отметки недопустимых условий, throw является подвыражением, которое является недопустимым как постоянное выражение, за исключением случаев, когда они скрыты на неоцененной стороне ?:, && или ||. Язык гарантирует, что это будет отмечено во время компиляции, даже если отладчик позволяет вам пройти через него во время выполнения и только если флаг действительно запущен.

Язык тут же подходит. К сожалению, это не может быть согласовано со специальной функцией диагностического сообщения static_assert или ветвлением на constexpr -ness.