Wrapper classes for primitive data types

When developing a solution, it is sometimes convenient to provide wrapper classes for primitive data types. Consider a class that represents a numeric value, be it double , a float or int .

 class Number { private: double val; public: Number(int n) : val(n) { } Number(float n) : val(n) { } Number(double n) : val(n) { } // Assume copy constructors and assignment operators exist Number& add(const Number& other) { val += other.val; return *this; } int to_int() const { return (int) val; } float to_float() const { return (float) val; } double to_double() const { return val; } }; 

Now suppose I have a function as such:

 void advanced_increment(Number& n) { n.add(1); } 

And I would use this function as such:

 Number n(2); advanced_increment(n); // n = 3 

It sounds simple enough. But what if the function was like that?

 void primitive_increment(int& n) { ++n; } 

Note that increment is an example. It is assumed that the function will perform more complex operations with primitive data types, which they can also perform on Number types without any problems.

How will I use the function in the same way as before? How in:

 Number n(2); primitive_increment(n); 

How can I map the Number class to primitive_increment ? How to create a wrapper class for primitive data types that will be compatible wherever these data types are required?

So far, I have found only two solutions. One of them is to create a function such as double& Number::get_value() , and then use it as primitive_increment(n.get_value()); . The second solution is to create implicit conversion methods, such as Number::operator int&() ; but this can lead to many ambiguous calls and make the code confusing.

I am wondering if there is another solution for implementing these types of shells and preserving their primitive functions.

Update:

For further clarification in this project, the goal here is to create all data types derived from one base class, which is usually called Object when developing such a solution. The limitation is that no external library should be used. Therefore, if I have a container with pointers to the Object type, it should be able to hold any arbitrary value, primitive or not, and perform any primitive operation that is permitted on Object . Hope this explains it better.

+6
source share
7 answers

C ++ 11 has explicit operator overloads.

 struct silly_wrapper { int foo; explicit operator int&() { return foo; } }; void primitive_increment(int& x) { ++x; } int main() { silly_wrapper x; primitive_increment(x); // works x += 1; // doesn't work - can't implicitly cast } 
+1
source
 class Number { enum ValType {DoubleType, IntType} CurType; union { double DoubleVal; int IntVal; }; public: Number(int n) : IntVal(n), CurType(int) { } Number(float n) : DoubleVal(n), CurType(DoubleType) { } Number(double n) : DoubleVal(n), CurType(DoubleType) { } // Assume copy constructors and assignment operators exist Number& add(const Number& other) { switch(CurType) { case DoubleType: DoubleVal += other.to_double(); break; case IntType: IntVal+= other.to_int(); break; } return *this; } int& to_int() { switch(CurType) { case DoubleType: IntVal = DoubleVal; CurType = IntType; break; //case IntType: DoubleVal = IntVal; CurType = DoubleType; break; } return IntVal; } const int to_int() const { switch(CurType) { case DoubleType: return (int)DoubleVal; case IntType: return (int)IntVal; } } const float to_float() const { switch(CurType) { case DoubleType: return (float)DoubleVal; case IntType: return (float)IntVal; } } double& to_double() { switch(CurType) { //case DoubleType: IntVal = DoubleVal; CurType = IntType; break; case IntType: DoubleVal = IntVal; CurType = DoubleType; break; } return DoubleVal; } const double to_double() const { switch(CurType) { case DoubleType: return (double)DoubleVal; case IntType: return (double)IntVal; } } }; void primitive_increment(int& n) { ++n; } int main() { Number pi(3.1415); primitive_increment(pi.to_int()); //pi now is 4 return 0; } 

I admit that this is rather inconvenient, and not an ideal situation, but it solves this problem.

+1
source

Instead of providing primitive_increment . You must overload the ++ operator for your Number class and increment it this way.

 Number& operator++() { ++val; return *this;} Number& operator+=(const Number& rhs) { val += rhs.Val; return *this;} Number operator+(const Number& rhs) { Number t(*this); t+=rhs; return t;} 

see Operators in C and C ++

0
source

If your Number class does not implement a subset of int , you simply cannot do this. This would give incorrect results if, for example, your Number class contains the value INT_MAX and can also hold the value INT_MAX+1 . If your Number class models a subset of int , then converting to and from int , of course, an option.

Other than that, your only chance is to rewrite the function to accept Number objects. It's ideal to make it a template so that it works with both int and Number (as well as with any other current or future class that represents the int interface).

0
source

Make the conversion statement private and use the friend function to convert inside it.

 class silly_wrapper { private: int foo; float bar; operator int&() { return foo; } template <typename T> friend void primitive_increment(T& x) { ++static_cast<int&>(x); } }; int main() { silly_wrapper x; primitive_increment(x); // works int i; primitive_increment(i); // works int& r = static_cast<int&>(x); // can't convert - operator is private } 
0
source

Here's even more of the answer to bizzare I was just thinking about:

 class Number; template<class par, class base> class NumberProxy { base Val; par* parent; NumberProxy(par* p, base v) :parent(p), Val(v) {} NumberProxy(const NumberProxy& rhs) :parent(rhs.parent), Val(rhs.Val) {} ~NumberProxy() { *parent = Val; } NumberProxy& operator=(const NumberProxy& rhs) {Val = rhs.Val; return *this} operator base& {return Val;} }; class Number { private: double val; public: Number(int n) : val(n) { } Number(float n) : val(n) { } Number(double n) : val(n) { } // Assume copy constructors and assignment operators exist int to_int() const { return (int) val; } float to_float() const { return (float) val; } double to_double() const { return val; } NumberProxy<Number,int> to_int() { return NumberProxy<Number,int>(this,val); } NumberProxy<Number,float> to_float() { return NumberProxy<Number,float>(this,val); } NumberProxy<Number,double> to_double() { return NumberProxy<Number,double>(this,val); } }; void primitive_increment(int& n) { ++n; } int main() { Number pi(3.1415); primitive_increment(pi.to_int()); //pi now is 4 return 0; } 

Number.to_int() returns a NumberProxy<int> , which is an implication convertible to int& , on which the function runs. When the function and expression are completed, the temporary NumberProxy<int> destroyed, and it destroys its parent Number with the updated value. This added convenience only for minor modifications to the Number class.

Obviously, there is a certain danger here if you call to_N() twice in the same expression, two int & will not synchronize, or if someone takes int & at the end of the statement.

0
source

(This is a little shot in the dark, as I'm not quite sure how your overall design fits together.)

How about generic free features:

 class IncTagIntegral{}; class IncTagNonintegral{}; template <bool> struct IncTag { typedef IncTagNonintegral type; } template <> struct IncTag<true> { typedef IncTagIntegral type; } template <typename T> void inc_impl(T & x, IncTagIntegral) { ++x; } template <typename T> void inc_impl(T & x, IncTagNonintegral) { x += T(1); } template <typename T> void primitive_increment(T & x) { inc_impl<T>(x, typename IncTag<std::is_integral<T>::value>::type()); } template <> void primitive_increment(Number & x) { // whatever } 

This approach can be generalized to other functions that need to be applied to both existing types and your own types.


Here is another long shot, this time using style erasure:

 struct TEBase { virtual void inc() = 0; } struct any { template <typename T> any(const T &); void inc() { impl->inc(); } private: TEBase * impl; }; template <typename T> struct TEImpl : public TEBase { virtual void inc() { /* implement */ } // ... }; // and provide specializations! template <typename T> any::any<T>(const T & t) : impl(new TEImpl<T>(t)) { } 

The key is that you provide various concrete implementations of TEImpl<T>::inc() with specialization, but you can use a.inc() for any object a type any . You can create additional wrappers for a free function in this idea, for example void inc(any & a) { a.inc(); } void inc(any & a) { a.inc(); } .

0
source

Source: https://habr.com/ru/post/901075/


All Articles