Skip to main content
gamedev.net
🔒 Locked

explicit-izing C++

Started by shukapi 13 replies 3.1k views
Original Post
shukapi
shukapi
Hi I'm dealing with lots of mathematical templates. Frequently I am having to avoid erroneous implicit type conversions. Unfortunately, there are a few times when implicit type conversion is desirable. Mainly: 1) Literals like "int x = 50" (which is better than "int x = int(50)"). 2) Array and vector indexes and pointer arithmetic. (Is it indexes or indices?) And AFAIK there is no way to specify "no explicit conversions" except in class constructors. Does anyone know any nifty tricks for avoiding unnecessary implicit conversions? My current backup method is:

template <typename T>
class CExplicit {
  public:
    CExplicit(const T &value) : m_value(value) {}
    operator T() { return m_value; }
    T m_value;
};

class CFoo {
  public:
    explicit CFoo(int value) : m_value(value) {}
    // or CFoo(CExplicit<int> value) : m_value(value) {}
    operator CExplicit<int>() const { return m_value; }
    int m_value;
};

(Syntax might not be 100%, typing this from memory on another PC.) However I'm pretty sure (haven't checked yet) that this method creates additional overhead. Anyone know of any other tricks? Thanks in advance.
Antheus
Antheus
There should be no overhead if you use only custom types and pass them around via reference. Compiler will likely treat them properly.

template < typename T >class StronglyTyped{public:  explicit StronglyTyped( T t ) : m_value(value) ()  explicit StronglyTyped( const StronglyTyped<T> &other ) : m_value(other.m_value) ()  ...  // define arithmetic operators  ...  double asDouble() const  {    return static_cast<double>(m_value);  }  StronglyTyped<double> toDouble() const  {    return StronglyTyped<double>(m_value);  }  // and so on...private:  T m_value;}


The constructors ensure that only same types can be assigned. You can then add explicit assignments as needed. (template < typename U> from( U u ), for example).

Any modern compiler will optimize the classes away, leaving you with pure logic. All your math is then done exclusively with these classes. Something like:
typedef StronglyTyped<int> Integer;typedef StronglyTyped<float> Float;typedef StronglyTyped<double> Double;


I haven't tested this, but it should work, possibly with a tweak or two. It's also a convenient way to do units the normal way, not via template metas.
shukapi
shukapi
Quote:

There should be no overhead if you use only custom types and pass them around via reference. Compiler will likely treat them properly.


That's good to know. What about conversions to array or std::vector indexes (unsigned int) though? Hopefully there's no copy-constructor invoked.


I'm starting a strongly typed template like what you have shown below. After I finish I'll try a few speed tests and have a look at the ASM to make sure there's no overhead or very minimal.

Should be great.
No more implicit bool conversions or loss-of-precision.

Thanks.
Antheus
Antheus
Quote:
No more implicit bool conversions or loss-of-precision.


You do have your warning set to maximum, right?
And you do look at every warning issued, and fix it?

Because those two problems are both common problems detected by compilers.

With wrapped classes you won't get any more benefit.
Jerax
Jerax
Quote:
Original post by shukapi
Unfortunately, there are a few times when implicit type conversion is desirable. Mainly:
1) Literals like "int x = 50" (which is better than "int x = int(50)").
2) Array and vector indexes and pointer arithmetic. (Is it indexes or indices?)


1. there is no implicit conversion in your example - 50 has type int so it's just a direct assignment.
2. It's indices. Array/vector indices and pointer arithmetic shouldn't have any implicit conversions either, if you use the correct types.
Julian90
Julian90
Quote:
That's good to know. What about conversions to array or std::vector indexes (std::vector::size_type) though? Hopefully there's no copy-constructor invoked.


Fixed.

Quote:
I'm starting a strongly typed template like what you have shown below. After I finish I'll try a few speed tests and have a look at the ASM to make sure there's no overhead or very minimal.

Should be great.
No more implicit bool conversions or loss-of-precision.


If thats the only reason your doing this then I wouldn't bother, as Antheus said those are errors commonly detected as warnings by compilers, a more common (and useful) use for this is to allow for dimensional analysis like the following.

template< typename Real        , int mass        , int length        , int time        , int charge        , int temperature        , int intensity        , int angle        >struct dimension;typedef dimension<float, 0, 0, 0, 0, 0, 0, 0> scalar;typedef dimension<float, 1, 0, 0, 0, 0, 0, 0> mass;typedef dimension<float, 0, 1, 0, 0, 0, 0, 0> length;typedef dimension<float, 0, 0, 1, 0, 0, 0, 0> time;// ...typedef dimension<float, 1, 1, -2, 0, 0, 0, 0> force;typedef dimension<float, 0, 1, -1, 0, 0, 0, 0> speed;// ...force calculate_gravity(mass m1, mass m2, length r){    //     G * m1 * m2    // F = -----------    //        r^2    dimension<float, -1, 3, -2, 0, 0, 0, 0> G = 6.6742e-11;    return G * m1 * m2 / (r * r);    // But if we made a mistake and wrote    // return G * m1 * m2 / r * r;    // It would cause a compiler error.}
JohnBolton
JohnBolton
Quote:
Original post by Jerax
Quote:
Original post by shukapi
(Is it indexes or indices?)

2. It's indices.

According to the Merriam-Webster dictionary, it is both.
Quote:

Main Entry: in·dex
Pronunciation: 'in-"deks
Function: noun
Inflected Form(s): plural in·dex·es or in·di·ces

John BoltonLocomotive Games (THQ)Current Project: Destroy All Humans (Wii). IN STORES NOW!
Jaiminho
Jaiminho
Quote:
Original post by shukapi
1) Literals like "int x = 50" (which is better than "int x = int(50)").


Expression A is the same as expression B w for any type:

A) Type var = value;
B) Type var(value);

Therefore, int x = int(50) constructs an int with 50 and then assigns through the copy constructor to the newly created int. But no compiler does anything this way (I hope) and simply creates an int with value 50.

This points to these 3 expressions being equivalent:

A) Type var = value;
B) Type var(value);
C) Type var = Type(value);
Antheus
Antheus
Quote:
Original post by Jaiminho
This points to these 3 expressions being equivalent:

A) Type var = value;
B) Type var(value);
C) Type var = Type(value);


This isn't necessarily true for non-basic types, where either assignment or copy constructor will be invoked.
mfawcett
mfawcett
You may want to take a look at the Boost Units library. I think you can get a relatively recent version from the Vault, and the most recent version from CVS (starting Tuesday the CVS repository will be closed, so grab it from Subversion after that).

This syntax may have changed since, but here's a quick snippet of what you can do.
#include <iostream>#include <boost/units/io.hpp>#include <boost/units/unit.hpp>#include <boost/units/quantity.hpp>#include <boost/units/systems/si.hpp>using namespace boost::units;using namespace boost::units::SI;typedef quantity<power> power_t;typedef quantity<area> area_t;typedef quantity<temperature> temp_diff_t;typedef typeof(watts/kelvin/pow<2>(meter))heat_transfer_coefficient_unit_type;typedef quantity<heat_transfer_coefficient_unit_type>heat_transfer_coefficient_t;int main(){     const heat_transfer_coefficient_twatts_per_square_meter_per_kelvin = 1.0*watts/square_meter/kelvin;     std::cout << heat_transfer_coefficient_unit_type() << std::endl               << watts_per_square_meter_per_kelvin << std::endl               << std::endl;     return 0;}outputtingkg s^(-3) K^(-1)1 kg s^(-3) K^(-1) 

--Michael Fawcett
Julian90
Julian90
Quote:
Original post by Antheus
Quote:
Original post by Jaiminho
This points to these 3 expressions being equivalent:

A) Type var = value;
B) Type var(value);
C) Type var = Type(value);


This isn't necessarily true for non-basic types, where either assignment or copy constructor will be invoked.


Actually they are equivalent, according to the standard the compiler is allowed to omit the call to the copy constructor in those and a limited number of other cases (the NRVO comes to mind, not sure exactly where else though) even if the copy constructor may have side effects.
shukapi
shukapi
Quote:

A) Type var = value;
B) Type var(value);
C) Type var = Type(value);


A won't work if the cast from the type of "value" to the type of "var" is defined as explicit.



After playing with my wrapper class it seems that I wasn't able to do what I thought I could do.

A cast from type A to type B that is defined inside A (as A::operator B()) cannot be used as an implicit second conversion. IE, even if B(C) is defined as any type of conversion, A(C) will not compile.

The problem is that since A is typically "bool" or "int" or something, A(B) can only be defined as A::operator B().

However, bool(T*) is defined (where T is any type).

NOTE: When using -O3 I saw no efficiency concerns in the ASM with using a wrapper class for integers and bools, there was even loop unrolling I think.

I hear std::stream classes use this hack for their implicit bool conversion.
class CBar {  private:    CBar();    ~CBar();};class CFoo {  public:    operator CBar*() {    bool m_foo;};int main() {    CFoo x;    if(x) return 1;    return 0;}

The above should compile (there might be errors, this machine doesn't have C++ installed).



Ok I guess I should ask some questions:

1) Does anyone know something that implicitly converts to int or unsigned int or the like, (like void* converts to bool). So that array[X] is valid but bool(X) is not.

2) It seems strange to me that this problem has had to be hacked by std:: since it was written (I think), yet there is still no fix - where do you submit ideas about what should be in the latest versions of C++?

Emmanuel Deloget
Emmanuel Deloget
Quote:
Original post by shukapi
I hear std::stream classes use this hack for their implicit bool conversion.

Most recent implementation shall use a conversion to a pointer to a member in order to be even safer. Since it's a pointer it can be tested against NULL but it can't be converted to anything else. At least, it's what the TR1 says (but don't remember what the standard says, and I don't want to search for an exact quote).

Quote:
Original post by shukapi
1) Does anyone know something that implicitly converts to int or unsigned int or the like, (like void* converts to bool). So that array[X] is valid but bool(X) is not.

If you can convert to an integral type, you can convert to a bool. Pretty much everything can be converted to a bool. The standard says (4.12):
"An rvalue of arithmetic, enumeration, point, or pointer to member type can be converted to an rvalue of type bool. A zero value, null pointer value, or null member pointer is converted to false; any other value is converted to true."
An arithmetic type is one of: bool, char, wchar_t, a signed integral type, an unsigned integral type, float, double, long double.

Quote:
Original post by shukapi
2) It seems strange to me that this problem has had to be hacked by std:: since it was written (I think), yet there is still no fix - where do you submit ideas about what should be in the latest versions of C++?

To the WG21, via comp.std.c++.

Anyway, I don't get your point here: do you expect the compiler to try out every class in your application in order to find one conversion operation that will match his need? The compiler will not do that. The Holy Book explicitly restricts implicit user conversions (§13.3.3.1.2):
"A user-defined conversion sequence consists of an initial standard conversion sequence followed by a user-defined conversion followed by a second standard conversion sequence."
Standard conversion sequences (described in §13.3.3.1.1) are related to base type conversions, lvalue-to-rvalue, array-to-pointer conversions and cv qualification conversions.

HTH,
shukapi
shukapi
Quote:

Anyway, I don't get your point here: do you expect the compiler to try out every class in your application in order to find one conversion operation that will match his need?

Damn, yep that's what I expected it to do :).

Quote:

The Holy Book

Is this Bjarne's "The C++ Programming Language"? Or the ISO C++ Standard? Is it a good bedtime read? (Just downloading the standard now on the world's slowest modem).

Quote:

HTH

Yeah, heaps. Made it clearer what I should be trying for. I think now that the benefits of wrapped types aren't worth the cost now. (I don't like if(int), I much prefer if(int == 0), but it seems that if(int) was definitely the intent).
Emmanuel Deloget
Emmanuel Deloget
Quote:
Original post by shukapi
Is this Bjarne's "The C++ Programming Language"? Or the ISO C++ Standard? Is it a good bedtime read? (Just downloading the standard now on the world's slowest modem).

Bjarne's book is relatively saint. But the Holy Book is the book from where everything come - the ISO/IEC 14882:2003(E) internation standard, "Programming Languages - C++" (which is available as a paying download (356 CHF) on iso.org or as a PDF draft (1998 edition) on wg21's web).

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.