template conversion operator clarification needed

V

Vijai Kalyan

Hello,

I have come back to C++ after a couple of years with Java so I am quite
rusty and this question may seem poor:

My platform is Windows XP with MSVC 7.1.

I have a class with a templatized conversion operator defined as
follows:

----------------------------------------------------------
template<class T>
class IBitImpl : public virtual IBit<T>
{
std::numeric_limits<T> m_limits;
T m_bit;
// other fns/ctor's etc

template<class C> operator C() const
throw(InvalidCastException)
{
if(std::numeric_limits<C>::digits < m_limits.digits)
{
throw InvalidCastException(L"Invalid argument -
Destination type C cannot accomodate source type T. ");
}
return (C) m_bit;
}
};
----------------------------------------------------------

So, I up and write a simple test:

----------------------------------------------------------
int x = 121234234;
IBitImpl<int> lowbit2(2);
bool isset = (1 == (x & lowbit2));
std::cout<<"Is bit "<<lowbit2.Bit()<<" in "<<x<<" set?
"<<std::boolalpha<<isset<<std::endl;
----------------------------------------------------------

I get an error indicating that there is an ambiguity because in (x &
lowbit2) the conversion can be any one of (long, bool, unsigned long,
etc).

I thought that the compiler looks at x and upcasts lowbit2 to the
appropriate type (in this case int). This doesn't seem to be the case
here.

I figured the whole problem ws because of the conversion operator
defined above. Consequently, I changed the expression to make the case
explicit as follows:

---------------------------------------------------------
bool isset = (1 == (x & (int) lowbit2));
---------------------------------------------------------

which needless to say, worked fine. However, I don't understand why the
correct cast isn't automatically applied. I am missing something here
and would appreciate it if someone would point me in the right
direction.

thanks,

-vijai.
 
V

Vijai Kalyan

My stupidity. The code snippet for the expression should be

bool isset = ( (x & (int) lowbit2));

However, that doesn't detract from my original question.

-vijai.
 
A

Alf P. Steinbach

* Vijai Kalyan:
I have come back to C++ after a couple of years with Java
Noted.


so I am quite
rusty and this question may seem poor:

My platform is Windows XP with MSVC 7.1.
Irrelevant.


I have a class with a templatized conversion operator defined as
follows:

Naming: "Bit" would be a good name for this class.
Performance/design: difficult to see any advantage in having IBit said:
{
std::numeric_limits<T> m_limits;

Should be a 'typedef' (if anything), not a data member.


Naming: is this the bit number, let's call it n, or is it the bit
value 1<<n?

Only for the latter case does it make sense to use type T.

In the test program it seems there is a member function that computes n from
m_bit, which is a bit awkward, opposite of the simplest & most efficient.

// other fns/ctor's etc

template<class C> operator C() const
throw(InvalidCastException)

Javaism : avoid throw specifications in C++, except 'throw()'.
Design : don't be afraid to use 'assert'.
Types : if possible use standard exception classes or classes derived from
them, e.g. here, std::bad_cast (but: rather 'assert' instead).
{
if(std::numeric_limits<C>::digits < m_limits.digits)

See above regarding 'm_limits'.

{
throw InvalidCastException(L"Invalid argument -
Destination type C cannot accomodate source type T. ");

See above regarding exception types and use of 'assert'.

}
return (C) m_bit;

Javaism (and also C-ism): don't use C-style casts. Use e.g. static_cast.

See above regarding naming and resulting confusion about what 'm_bit' is.
}
};
----------------------------------------------------------

So, I up and write a simple test:

----------------------------------------------------------
int x = 121234234;
IBitImpl<int> lowbit2(2);
bool isset = (1 == (x & lowbit2));
std::cout<<"Is bit "<<lowbit2.Bit()<<" in "<<x<<" set?
"<<std::boolalpha<<isset<<std::endl;
----------------------------------------------------------

I get an error indicating that there is an ambiguity because in (x &
lowbit2) the conversion can be any one of (long, bool, unsigned long,
etc).

Yes. Built-in operators do not constrain the types of their arguments,
except to the set of types the operator in question is defined for.
However, if you define your own '&' operator you can do that, e.g.

template< typename T, typename U >
T operator&( T a, Bit<U> const& b ){ return a & static_cast<T>( b ); }

template< typename T, typename U >
I thought that the compiler looks at x and upcasts lowbit2 to the

'upcast' is something else entirely; here you're talking about a conversion,
in no particular direction.

appropriate type (in this case int). This doesn't seem to be the case
here.

I figured the whole problem ws because of the conversion operator
defined above. Consequently, I changed the expression to make the case
explicit as follows:

There is no correct conversion because there is no constraint whatsoever
(except to the set of types that the built-in '&' is defined for).

I am missing something here
and would appreciate it if someone would point me in the right
direction.

See above.
 
V

Vijai Kalyan

----------------------------------------------------------
Naming: "Bit" would be a good name for this class.
Performance/design: difficult to see any advantage in having IBit<T>.

Well, IBit is really the interface that defines the requirements for
IBitImpl.

template<class T>
class IBit
{
public:
template<class C> IBit<T>& operator=(C);
template<class C> IBit<T>& operator=(const IBit<C>&);
template<class C> operator C() const;

virtual inline T Bit() const = 0;
virtual inline T Mask() const = 0;
};

Your are right. A member is not called for.
Should be a 'typedef' (if anything), not a data member.



Naming: is this the bit number, let's call it n, or is it the bit
value 1<<n?

m_bit is the bit number. There is a data member m_mask that stores the
mask.
Only for the latter case does it make sense to use type T.

Here, I don't know. I presume an 'int' or 'short' can equally hold all
possible bit numbers.
In the test program it seems there is a member function that computes n from
m_bit, which is a bit awkward, opposite of the simplest & most efficient.

No. The mask is computed when the object is created and stored in
m_mask of type T.
Javaism : avoid throw specifications in C++, except 'throw()'.
Design : don't be afraid to use 'assert'.
Types : if possible use standard exception classes or classes derived from
them, e.g. here, std::bad_cast (but: rather 'assert' instead).

True. I should have used bad_cast. On the other hand, most of the
standard exception classes don't seem to have a wide-string variant of
their constructors. I would rather not mix the two and prefer to use
the wide-string variant where possible. Of course, that is irrelevant
strictly speaking. I can derive form bad_cast and provide a
wide-string constructor.
See above regarding 'm_limits'.



See above regarding exception types and use of 'assert'.



Javaism (and also C-ism): don't use C-style casts. Use e.g. static_cast.

My mistake.
See above regarding naming and resulting confusion about what 'm_bit' is.

As I said, m_bit is the bit number. m_mask is the actual mask. So,

m_mask = (1 << m_bit);
Yes. Built-in operators do not constrain the types of their arguments,
except to the set of types the operator in question is defined for.
However, if you define your own '&' operator you can do that, e.g.

template< typename T, typename U >
T operator&( T a, Bit<U> const& b ){ return a & static_cast<T>( b ); }

template< typename T, typename U >


'upcast' is something else entirely; here you're talking about a conversion,
in no particular direction.

Ah, I see. Ok. Well, I thought about defining those operators and then
started wondering why the conversion operator wouldn't work in general.
Of course, that means I would have to define a | operator as well.
There is no correct conversion because there is no constraint whatsoever
(except to the set of types that the built-in '&' is defined for).



See above.

Great! Thanks a lot for the clarifications.

-vijai.
 
A

Alf P. Steinbach

* Vijai Kalyan:
My mistake.


As I said, m_bit is the bit number. m_mask is the actual mask. So,

m_mask = (1 << m_bit);

In that case, in the test program's


the sub-expression (I've added the necessary static_cast)

x & static_cast<int>( lowbit2 )

will not give correct result, because x isn't and'ed with a mask but with a
bitnumber.

This particular bug would probably have been avoided with more descriptive
naming... ;-)

There's also another bug in that expression, namely, the comparision with 1.

To avoid silly warnings from the compiler you can

A) compare the '&' result with the mask, or
B) compare the '&' result with 0, or
C) apply the negation operator '!' twice to the '&' result.

To avoid such bugs in general, try to design classes where it requires great
effort to use them incorrectly -- e.g., take control of all relevant ops.
 
V

Vijai Kalyan

As a matter of fact, it is kind of funny.

The conversion operator actually returns the "mask" instead of the
"bit". So,

(x & lowbit2)

would work.

But that is not really why I replied.

Considering that in the above expression, an explicit cast is required
to use the mask writting the correct expression as:

x & (int) lowbit2

really enforces a certain amount of safety primarily because the
conversion operator applied checks to make sure that the destination
type (in this case int) can actually hold the mask. I would think that
is actually a "good side-effect" of the conversion operator although I
am not too sure.

For example, the following would most definitely throw a runtime
exception "bad_cast"

char x = 'a';
x & (char) lowbit2.

Likewise, so would the following:

IBitImpl<int> lowbiti(2);
IBitImpl<char> lowbitc = lowbiti;

This of course not being due to the conversion operator but due to the
templatized constructor:

template<typename C> IBitImpl(const IBitImpl<C>&)

which also performs the check that C can hold T.

However, the following wouldn't:

IBitImpl<char> lowbitc(2);
IBitImpl<int> lowbiti = lowbitc;

since an integer mask can certainly accomodate a char mask.

Interesting no?

-vijai.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,537
Members
45,022
Latest member
MaybelleMa

Latest Threads

Top