constexpr, static_assert и встраивание

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

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

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

Поэтому, если бы был какой-то способ проверить, является ли условие constexpr (из-за встраивания или других оптимизаций), я мог бы вызвать static_assert когда это возможно, и отступить во время выполнения утверждать иначе. К счастью, у gcc есть свойственное __builtin_constant_p (exp), который возвращает истину, если 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 заявление (что не удается). Тем не мение,

[david@david-desktop 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)? Я чувствую, что мои C++ проекты теоретически могли бы извлечь большую выгоду из более умного утверждения assert, которое ловит ошибки как можно раньше.

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

3 ответа

Это должно помочь вам начать

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

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

Явное - это хорошо, неявное - это плохо, в общем.

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

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

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

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

#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.


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

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

Выше, насколько я получил с поддержкой g ++. Это хорошо работает (решает проблему ОП) для Visual C++, используя идею, которую я представил. Но не с 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 \ тест]
> g++ foo.cpp -Werror= деление на ноль -D DYNAMIC_FALSE

[D: \ DEV \ тест]
> а
isConstExpr = true
Уже должен был утверждать.

[D: \ DEV \ тест]
> _

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

Argh.


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

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

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

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

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

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

Другие вопросы по тегам