Как заставить C ++ создавать выражение, которое использует проверку времени компиляции для констант и утверждения для переменных? - PullRequest
0 голосов
/ 04 августа 2010

Вот пример настройки ... макрос или шаблон CHECKEXPR_RETURNVAL (EXPR, VAL), который проверяет, что EXPR равен TRUE при возврате VAL.

Это полезно во многих местах - как в этом очень упрощенном примере:

#define ISPOW2(VAL)           ((0!=VAL)&&(0==(VAL&(VAL-1))))
#define _ALIGNPOW2(VAL,ALIGN) ((VAL+(ALIGN-1))&(~(ALIGN-1)))

#define ALIGNPOW2(VAL,ALIGN)  CHECKEXPR_RETURNVAL( \
    ISPOW2(ALIGN) , _ALIGNPOW2(VAL,ALIGN) )

Итак, трудность заключается в следующем: Я хочу сделать проверки времени компиляции, если это возможно, и если значение не является константой, определяемой во время компиляции, то выполнить проверку во время выполнения.

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


Вот мои две (неудачные) попытки заставить одну версию работать в нескольких местах (как с постоянным размером массива, как инициализатор перечисления и как функция с переменными). К сожалению, они либо работают только во время компиляции (постоянный инициализатор) или только во время выполнения - я хотел бы выяснить версию, которая будет работать для обоих.

// CHECKEXPR_RETURNVAL - version "A"
#define CTCHECK_EXPR(EXP)(CTCheckBool<EXP>::ExistsZeroIfTrue)
template <bool bExpression> struct CTCheckBool {};
template <> struct CTCheckBool<true> {enum{ExistsZeroIfTrue=0};};
// Note: Plus ("+") is used rather than comma operator because
// the comma operator can not be used for constant initializers
#define CHECKEXPR_RETURNVAL_A(EXP,VAL) (CTCHECK_EXPR(EXP) + (VAL))

// Test Out version "A" -- works only for Compile Time Constants
#define ALIGNPOW2_A(VAL,ALIGN)  CHECKEXPR_RETURNVAL_A( \
    ISPOW2(ALIGN) , _ALIGNPOW2(VAL,ALIGN) )

char AlignedVar_A[ALIGNPOW2_A(2,8)];
enum { AlignedVal_A = ALIGNPOW2_A(57,16) };

int TestAlignPow2_A(int val, int align)
    {return(ALIGNPOW2_A(val,align));}       // Compile Error


// CHECKEXPR_RETURNVAL - version "B"
template<typename T> T CHECKEXPR_RETURNVAL_B(bool bExpr,T val)
    { ASSERT(bExpr); return(val); }

// Test Out version "B" -- works only for Runtime Computed Values
#define ALIGNPOW2_B(VAL,ALIGN)  CHECKEXPR_RETURNVAL_B( \
    ISPOW2(ALIGN) , _ALIGNPOW2(VAL,ALIGN) )

char AlignedVar_B[ALIGNPOW2_B(2,8)];        // Compile Error
enum { AlignedVal_B = ALIGNPOW2_B(57,16) }; // Compile Error

int TestAlignPow2_B(int val, int align)
    {return(ALIGNPOW2_B(val,align));}

К сожалению, ни одна из версий не работает для всех трех случаев. Существует ли структура кода, которая будет работать для всех случаев?

Ответы [ 2 ]

0 голосов
/ 04 августа 2010

Хорошо ... Не полный ответ, но я думаю, что вы можете получить то, что вы хотите из этого:

#include <stdio.h>

template <int I> struct S{
    static void doIt(){
        fprintf(stderr, "wow\n");
    }
};

template<> void S<0>::doIt(){
    fprintf(stderr, "oops\n");
}

#define EXPR(p) S<(int)((bool)(p))>::doIt()     

int main(int argc, char** argv){
    EXPR((5+2)==7);
    EXPR((5+2)==8);
    const int a = 58;
    EXPR(a==58);
    EXPR(58);
    return 0;
}

Вы можете получить ошибку компилятора на основе выражения:

#include <stdio.h>

template <int I> struct S{
    static void doIt(){
        ssdfkjehiu //deliberately invalid code
        fprintf(stderr, "oops\n");
    }
};

template<> void S<1>::doIt(){
    fprintf(stderr, "wow\n");
}

#define EXPR(p) S<(int)((bool)(p))>::doIt() 



int main(int argc, char** argv){
    EXPR((5+2)==7);
    EXPR((5+2)==8);//uncomment it to make code compile
    const int a = 58;
    EXPR(a==58);
    EXPR(58);
    return 0;
}

Но строка, которая вызывает ошибку, будет в середине длинного сообщения об ошибке шаблона. Пример:

1.cpp(6) : error C2065: 'asdlfkjhasd' : undeclared identifier
        1.cpp(4) : while compiling class template member function 'void S<I>::doIt(void)'
        with
        [
            I=0
        ]
        1.cpp(19) : see reference to class template instantiation 'S<I>' being compiled
        with
        [
            I=0
        ]
1.cpp(6) : error C2146: syntax error : missing ';' before identifier 'fprintf'

Как видите, ошибка вызвана строкой 19, которая упоминается в середине сообщения. Это немного неудобно.

Я не могу гарантировать, что оба примера не зависят от неопределенного поведения C ++.

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

P.S. Я думаю, что вы также должны взглянуть на повышение. Если я правильно помню, в нем было много «волшебного макроса препроцессора» (например, циклов), поэтому возможно, что он реализовал нечто подобное.

- изменить -
Хорошо, а как насчет этого ?:

#include <stdio.h>
#include <string>

template <typename T> void a(T &i){
    fprintf(stderr, "variable\n");  
}

void a(const char* const i){
    fprintf(stderr, "constant\n");
}

void a(bool i){
    fprintf(stderr, "constant\n");
}

int main(int argc, char** argv){
    int i;
    float f;
    std::string s;
    const char* s1 = "b";
    a(3);
    a(3+2);
    a(1.0f);
    a('a');
    a("b");
    a(i);
    a(f);
    a(s);
    a(s1);
    return 0;
}
0 голосов
/ 04 августа 2010

Похоже, вы действительно использовали бы функции c ++ 0x constexpr ...

...