A Proposal to Add Strong Type Aliases to the Standard Language

Started by
28 comments, last by Matt-D 11 years, 3 months ago

Hey, I want strong type aliases in C++. I've never written a standard proposal before, so here's my attempt.

View PDF online

Download: Word document (20.7kb)

Any suggestions, additions, rewordings, and etc... you are able to offer would be much appreciated. After the community reviews and improves it, I'll email it to the standards committee (which are currently accepting proposals for C++ TR2), so it can hopefully be read and discussed at a committee meeting for addition to the C++ standard.

Please download the document, annote it in red, and re-post it here. Alternatively, post suggestions in the thread itself.

I would really appreciate it! smile.png

Aside from non-static data member initializers, which was added into C++11 (thank you, whoever submitted that proposal!), strong typedefs are one of my most desired C++ features.

Advertisement

Hm... Can I ask why this feature is vital? Conversion between


typedef unsigned int Centimeters;
typedef unsigned int Inches;

are legal as for me, as the real types are the same.

For incompatible types gcc (at least) drops an error:


??[santa@yukio ~ $]
?
??> g++ test.cpp -o test -pedantic -Wall
test.cpp: In function ‘int main()’:
test.cpp:9:8: error: cannot convert ‘MyFloat {aka float}’ to ‘MyInt {aka void*}’ for argument ‘1’ to ‘void test(MyInt)’
??[santa@yukio ~ $]
?
??> cat test.cpp 
typedef void* MyInt;
typedef float MyFloat;

void test(MyInt) {
}

int main() {
        MyFloat f;
        test(f);
        return 0;
}

For compatible types you won't get an error even without typedefs:


??[santa@yukio ~ $]
?
??> g++ test.cpp -o test -pedantic -Wall
test.cpp: In function ‘int main()’:
test.cpp:9:8: warning: ‘f’ is used uninitialized in this function [-Wuninitialized]
??[santa@yukio ~ $]
?
??> cat test.cpp 
void test(int) {
}

int main() {
        float f;
        test(f);
        return 0;
}
You probably should read and reference previous opaque typedef proposals such as N1706 and N1891.
Thanks SiCrane, I assumed there were prior proposals but didn't find them. I'll read over them.
Hm... Can I ask why this feature is vital? Conversion between
typedef unsigned int Centimeters;
typedef unsigned int Inches;
are legal as for me, as the real types are the same.

For incompatible types gcc (at least) drops an error: ...

For compatible types you won't get an error even without typedefs: ...



That's precisely the point. I don't want Centimeters and Inches to be convertible, except explicitly. Strong aliases should refuse to compile aliases made even from the same base type. You need a function to convert from Centimeters to Inches, because 1 Centimeter is not 1 Inch, and doing myCentimeters = myInches is easy to do and almost always a bug.

[quote name='Servant of the Lord' timestamp='1356723633' post='5015148']
I don't want Centimeters and Inches to be convertible
[/quote]

Well and I don't understand why. As I wrote earlier built-in types with similar (in fact equal) semantics _are_ convertible. In your example you are trying to introduce a new type (not an alias in fact, alias is usually just another typename). Simple constructions such as `fancy_typedef old_type new_type' aren't able to describe the type semantics at all (both Centimeters and Inches are just lexems for compiler, you don't define what can be done to them). If you suggest semantics should be copied from the old_type than new_type would be just a typename, not a new type.


I don't want Centimeters and Inches to be convertible


Well and I don't understand why. As I wrote earlier built-in types with similar (in fact equal) semantics _are_ convertible.



You seriously don't understand why it's desirable to get a compiler error if someone has a quantity in centimeters and tries to use it where a quantity in inches is expected? I am not sure how else to explain it, since SOTL has been very clear.

[quote name='Álvaro' timestamp='1356725134' post='5015153']
You seriously don't understand why it's desirable to get a compiler error if someone has a quantity in centimeters and tries to use it where a quantity in inches is expected?
[/quote]

This is not my point. My point is thats not a compiler (c++ grammar) problem. Its worth implementing as a part of an STL.


You seriously don't understand why it's desirable to get a compiler error if someone has a quantity in centimeters and tries to use it where a quantity in inches is expected?


This is not my point. My point is thats not a compiler (c++ grammar) problem. Its worth implementing as a part of an STL.



Well, it's a problem where the compiler could help, if it implemented what we are discussing.

I have felt the need for something like this when I have classes Vector3D and Point3D, which are essentially the same thing, but I need to define them separately if I want the type system to help me make sure my operations make sense (e.g., you are not allowed to add points, but adding vectors is fine, and so is adding a point and a vector).

Since we are dealing with units, I just wrote this little test that seems to work fine:
#include <iostream>

namespace units {
  template <int m_pow, int kg_pow, int s_pow>
  struct unit {
    double value;
    explicit unit(double value) : value(value) {
    }
  };
  
  template <int m_pow, int kg_pow, int s_pow>
  unit<m_pow,kg_pow,s_pow> operator+(unit<m_pow,kg_pow,s_pow> u1, unit<m_pow,kg_pow,s_pow> u2) {
    return unit<m_pow,kg_pow,s_pow>(u1.value+u2.value);
  }
  
  unit<0,0,0> operator+(unit<0,0,0> u, double d) {
    return unit<0,0,0>(u.value+d);
  }
  
  unit<0,0,0> operator+(double d, unit<0,0,0> u) {
    return unit<0,0,0>(d+u.value);
  }
  
  template <int m_pow, int kg_pow, int s_pow>
  unit<m_pow,kg_pow,s_pow> operator-(unit<m_pow,kg_pow,s_pow> u1, unit<m_pow,kg_pow,s_pow> u2) {
    return unit<m_pow,kg_pow,s_pow>(u1.value-u2.value);
  }

  unit<0,0,0> operator-(unit<0,0,0> u, double d) {
    return unit<0,0,0>(u.value-d);
  }

  unit<0,0,0> operator-(double d, unit<0,0,0> u) {
    return unit<0,0,0>(d-u.value);
  }

  template <int m_pow1, int kg_pow1, int s_pow1, int m_pow2, int kg_pow2, int s_pow2>
  unit<m_pow1+m_pow2,kg_pow1+kg_pow2,s_pow1+s_pow2> operator*(unit<m_pow1,kg_pow1,s_pow1> u1, unit<m_pow2,kg_pow2,s_pow2> u2) {
    return unit<m_pow1+m_pow2,kg_pow1+kg_pow2,s_pow1+s_pow2>(u1.value*u2.value);
  }

  template <int m_pow, int kg_pow, int s_pow>
  unit<m_pow,kg_pow,s_pow> operator*(unit<m_pow,kg_pow,s_pow> u, double d) {
    return unit<m_pow,kg_pow,s_pow>(u.value*d);
  }

  template <int m_pow, int kg_pow, int s_pow>
  unit<m_pow,kg_pow,s_pow> operator*(double d, unit<m_pow,kg_pow,s_pow> u) {
    return unit<m_pow,kg_pow,s_pow>(d*u.value);
  }

  template <int m_pow1, int kg_pow1, int s_pow1, int m_pow2, int kg_pow2, int s_pow2>
  unit<m_pow1-m_pow2,kg_pow1-kg_pow2,s_pow1-s_pow2> operator/(unit<m_pow1,kg_pow1,s_pow1> u1, unit<m_pow2,kg_pow2,s_pow2> u2) {
    return unit<m_pow1-m_pow2,kg_pow1-kg_pow2,s_pow1-s_pow2>(u1.value/u2.value);
  }

  template <int m_pow, int kg_pow, int s_pow>
  unit<m_pow,kg_pow,s_pow> operator/(unit<m_pow,kg_pow,s_pow> u, double d) {
    return unit<m_pow,kg_pow,s_pow>(u.value/d);
  }

  template <int m_pow, int kg_pow, int s_pow>
  unit<m_pow,kg_pow,s_pow> operator/(double d, unit<m_pow,kg_pow,s_pow> u) {
    return unit<-m_pow,-kg_pow,-s_pow>(d/u.value);
  }

  template <int m_pow, int kg_pow, int s_pow>
  std::ostream &operator<<(std::ostream &os, unit<m_pow,kg_pow,s_pow> u) {  
    os << u.value;
    if (m_pow != 0) {
      os << "m";
      if (m_pow != 1)
	os << "^" << m_pow;
    }
    if (kg_pow != 0) {
      os << "Kg";
      if (kg_pow != 1)
	os << "^" << kg_pow;
    }
    if (s_pow != 0) {
      os << "s";
      if (s_pow != 1)
	os << "^" << s_pow;
    }
    return os;
  }

  unit<1,0,0> meter(1);
  unit<1,0,0> centimeter(0.01);
  unit<1,0,0> inch(0.0254);
  unit<0,0,1> second(1.0);
  unit<0,0,1> minute(60.0);
  unit<0,0,1> hour(3600.0);
  unit<0,1,0> gram(0.001);
  // etc.
}

using namespace units;

int main() {
  std::cout << "100 inches/hour = " << (100.0*inch/hour)/(centimeter/minute) << " centimeters/minute\n";
}

Does anyone know if there is a C++ library that does this type of thing? (Oh, and sorry about the hijack...)
boost::units seems to do what you want.

It would be cool if you could define casting operators for opaque typedefs too*:


// Obviously this would require a few changes to the Standard
Inches operator Centimeters() (Centimeters cm)
{
    // Estimate
    return (cm * 5) / 2;
    // Note that the above is merely example code; I realize that a) the above isn't necessarily the
    // the right data type, and b) casting it to the right data type could cause recursion without some
    // changes or facilities added to the Standard.
}
 
inline constexpr Centimeters operator"" _cm (int cm)
{
    return (Centimeters)cm;
}
 
Inches i = (Inches)(12_cm); // The point of this code is to show this line

Also, what would be the implications with promotions? What if you multiplied Inches (which is typedefd as an int) by a float? Or multiplied it by an int?

*This is, I would say, related to opaque typedefs, but is also a bit of a separate issue (because declaring a casting operator makes implicit conversions possible, and AFAIK you can't make a casting operator that requires explicit casting). I suppose you could also say it could work for non-opaque typedefs/implicit conversions, but I don't like the ambiguity of Inches i = 12_cm;

[size=2][ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]

This topic is closed to new replies.

Advertisement