2

I have a macro that is building a class for me. I want to provide a constructor which takes an int if the class itself does not have an int specified as its type. The macro looks something like:

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

I can come close using the boost preprocessor to manually turn this constructor on and off...

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; }; 

However, I cannot replace the 0 in the macro with a conditional. I want something like:

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; }; 

However, that expands to something less than helpful:

 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)){}; 

Looking around, it doesn't seem as though BOOST_PP_NOT_EQUAL is intended for this type of comparison. (I am aware of macro expansion issues and have build some "IMPL" macros to try to get things expanded out further. I don't think that is the problem here, however.) Thoughts?

4
  • Is your exact problem just ambiguous constructor calls? And why not using templates? Commented Dec 24, 2015 at 14:38
  • 1
    I don't think you're going to have any luck comparing type names in the preprocessor unless you at least have a finite set of types. You can hand this off to the compilation step instead of the preprocessing step, though. Commented Dec 24, 2015 at 14:39
  • It is. I cannot use templates here because of other templated functions and classes needing to use these classes. This would then impose an order dependence on the #include's. It's a mess. That was my first attempt, actually. MSVC was happy, but GCC and Clang rightly complained. Commented Dec 24, 2015 at 14:41
  • @chris I also tried to use std::enable_if<std::is_same<. Granted, I am abusing the preprocessor here a bit, but I hope there is a way! There would be a finite list of types here (they would all be some sort of integer). How could I use that fact to help me? Commented Dec 24, 2015 at 14:44

2 Answers 2

1

In case anyone else has an issue where they would like this type of specialization, I wanted to post an answer. This solution will work if you have a finite set of types/strings you want to compare and know them at compile time. Here is the example that supports int and uint8_t. The special constructor will only be written for the non-int type.

#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); 

The macro expands to:

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; }; 
Sign up to request clarification or add additional context in comments.

Comments

0

I was able to workaround this by making explicitly-declared constructor from int less preferred. For this, I declare a simple class that can be constructed from int and converted back to int and then use this class instead of plain int in constructor:

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; \ }; 

This allows for

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

Live on Coliru

Though note that this will create a problem if you try to pass your own class with operator int() declared. You original approach could handle this (converting the class to int and calling int constructor), while my approach will not, as the compiler will not allow two user-defined conversions (to int and then to int_wrapper).

Also I now can not get the second constructor called at all, because if you have static_cast<TYPE>(x), this means that int should be convertible to TYPE for every TYPE you use, but then the first constructor is enough. However, if your second constructor is just a simplified example and you do not actually directly cast TYPE to int, then you might find my answer useful.

4 Comments

Thank you. This works perfectly. Appreciate the different angle to solve the problem!
Some test cases showed I was too anxious for the problem to be solved. I'll keep looking for a solution.
@DiB, also can you comment on last paragraph of my answer? Because with your static_cast, I can't think of a case when the constructor accepting int will be needed
I cleaned up my constructors and assignment operators, removing some unnecessary overloads and the static cast. Looking at this simple example helped simplify the real code. The Solution seems to be that the fancy macro magic was not needed and less overloads actually helped.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.