Условное расширение препроцессора C ++ на основе параметра

У меня есть макрос, который создает для меня класс. Я хочу предоставить конструктор, который принимает int, если сам класс не имеет int, указанного в качестве его типа. Макрос выглядит примерно так:

CLASS_DECLARE(NAME, TYPE)\
  class NAME { \
  public: NAME(const TYPE& x) : value(x) {}\
  private: TYPE value; };

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

CLASS_DECLARE(NAME, TYPE)\
  class NAME { \
  public: NAME(const TYPE& x) : value(x) {}\
  BOOST_PP_EXPR_IF(0, NAME(const int& x) : value(static_cast<TYPE>(x)) {})\
  private: TYPE value; };

Однако я не могу заменить 0 в макросе условным. Я хочу что-то вроде:

CLASS_DECLARE(NAME, TYPE)\
  class NAME { \
  public: NAME(const TYPE& x) : value(x) {}\
  BOOST_PP_EXPR_IF(BOOST_PP_NOT_EQUAL(TYPE, int), NAME(const int& x) : value(static_cast<TYPE>(x)) {})\
  private: TYPE value; };

Однако это расширяется до чего-то менее полезного:

    BOOST_PP_EXPR_IIF_BOOST_PP_BOOL_BOOST_PP_NOT_EQUAL_CHECK_BOOST_PP_NOT_EQUAL_int(0, 
BOOST_PP_NOT_EQUAL_int)(MyType(const int& x) : value(static_cast<int>(x)){};

Оглядываясь вокруг, кажется, что BOOST_PP_NOT_EQUAL не предназначен для такого типа сравнения. (Я знаю о проблемах с расширением макросов и создал несколько макросов «IMPL», чтобы попытаться расширить возможности. Я не думаю, что здесь проблема.) Мысли?


person DiB    schedule 24.12.2015    source источник
comment
Ваша точная проблема - это просто неоднозначные вызовы конструктора? А почему бы не использовать шаблоны?   -  person Petr    schedule 24.12.2015
comment
Я не думаю, что вам удастся сравнить имена типов в препроцессоре, если у вас не будет хотя бы конечного набора типов. Однако вы можете передать это этапу компиляции, а не этапу предварительной обработки.   -  person chris    schedule 24.12.2015
comment
Это. Я не могу использовать здесь шаблоны, потому что другие шаблонные функции и классы должны использовать эти классы. Тогда это наложит зависимость порядка от # include. Это беспорядок. Собственно, это была моя первая попытка. MSVC был доволен, но GCC и Clang справедливо жаловались.   -  person DiB    schedule 24.12.2015
comment
@chris Я также пробовал использовать std :: enable_if ‹std :: is_same‹. Конечно, я немного злоупотребляю препроцессором, но надеюсь, что способ есть! Здесь будет конечный список типов (все они будут целыми числами). Как я мог использовать этот факт, чтобы помочь мне?   -  person DiB    schedule 24.12.2015


Ответы (2)


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

#include <boost/preprocessor.hpp>

#define TYPE_IS_int 0
#define TYPE_IS_uint8_t 1

#define CLASS_DECLARE(NAME, TYPE)\
    class NAME {\
        public: NAME(const TYPE& x) : value(x) {}\
        BOOST_PP_EXPR_IF(BOOST_PP_CAT(TYPE_IS_, BOOST_PP_EXPAND(TYPE)), NAME(const int& x) : value(static_cast<TYPE>(x)) {})\
        private: TYPE value; };

CLASS_DECLARE(MyIntType, int);
CLASS_DECLARE(MyUint8Type, uint8_t);

Макрос расширяется до:

class MyIntType
{ 
    public: 
        MyIntType(const int& x) : value(x) {}  
    private: 
        int value; 
};

class MyUint8Type 
{ 
    public: 
        MyUint8Type(const uint8_t& x) : value(x) {} 
        MyUint8Type(const int& x) : value(static_cast<uint8_t>(x)) {} 
    private: 
        uint8_t value; 
};
person DiB    schedule 24.12.2015

Мне удалось обойти это, сделав явно объявленный конструктор из int менее предпочтительным. Для этого я объявляю простой класс, который можно построить из int и преобразовать обратно в int, а затем использовать этот класс вместо простого int в конструкторе:

struct int_wrapper {
    int value;
    operator int() const { return value; }
    int_wrapper(int x): value(x) {}
};

#define CLASS_DECLARE(NAME, TYPE)\
  class NAME { \
  public: \
    NAME(const TYPE& x) : value(x) {}\
    NAME(const int_wrapper& x) : value(static_cast<TYPE>(x)) {} \
  private: \
    TYPE value; \
  };

Это позволяет

CLASS_DECLARE(cfloat, float)
CLASS_DECLARE(cint, int)

int main()
{
    cfloat f1(1.0);
    cfloat f2(1);
    cint i(2);
}

Живите на Coliru

Однако учтите, что это создаст проблему, если вы попытаетесь передать свой собственный класс с объявленным operator int(). Ваш оригинальный подход мог бы справиться с этим (преобразование класса в int и вызов int конструктора), в то время как мой подход не будет, поскольку компилятор не позволит два определяемых пользователем преобразования (в int, а затем в int_wrapper).

Кроме того, теперь я вообще не могу вызвать второй конструктор, потому что, если у вас есть static_cast<TYPE>(x), это означает, что int должен быть преобразован в TYPE для каждого TYPE, которое вы используете, но тогда первого конструктора будет достаточно. Однако, если ваш второй конструктор является просто упрощенным примером, и вы на самом деле не приводите TYPE к int, мой ответ может оказаться для вас полезным.

person Petr    schedule 24.12.2015
comment
Спасибо. Это прекрасно работает. Оцените разные углы для решения проблемы! - person DiB; 24.12.2015
comment
Некоторые тестовые примеры показали, что я слишком беспокоился о решении проблемы. Я буду искать решение. - person DiB; 24.12.2015
comment
@DiB, также можешь прокомментировать последний абзац моего ответа? Потому что с вашим static_cast я не могу придумать случая, когда понадобится конструктор, принимающий int - person Petr; 24.12.2015
comment
Я очистил свои конструкторы и операторы присваивания, удалив некоторые ненужные перегрузки и статическое приведение. Этот простой пример помог упростить реальный код. Решение, похоже, состоит в том, что не было необходимости в причудливой магии макросов, и на самом деле помогло меньшее количество перегрузок. - person DiB; 24.12.2015