templates class friends strong types

Hello all, first a bit of background.

Land Surveying involves many types of: Angles, Bearings; Distances; Co-ordinates; and there are many types adjustments and corrections to all these. There are probably many surveying applications in which all of these types are of type double, and the applications will work just fine.

I want to write an API where if the developer tries to do something that doesn't make any sense, the code will not compile with quite good error messages. I will achieve this with: strong types (every distinct thing will have it's own type, not just double); and non type template parameters for some of the types; along with operator overloading that uses the template parameters. The motivation for this is that I have seen on a large construction project (50 billion $) code that was logically wrong to cause the value to be wrong, which meant that all the points transformed coordinates over the whole project for a period in excess of 5 years were wrong. The job had a project grid with a scale factor of 1.000'000, these points were transferred to UTM grid with an incorrect scale factor, because the calculation of the value of the scale factor required an ellipsoidal height, not a mean sea level (MSL)which is rather different.

So an example of a thing that doesn't make sense: some operations with Universal Transverse Mercator (UTM)grid coordinates. It makes no sense to add world coordinates, but subtracting them is OK because it yields a Delta, Likewise adding a Delta to a world coordinate gives another world coordinate. Now UTM is split into 60 zones around the world, each 6 degrees of longitude wide; Zone 1 starts at the international date line (180 degrees West); Zones 30 and 31 are straddled by the Greenwich meridian in London; Zone 60 finishes at the international dateline (180 degrees East). Each Zone has a central meridian whose is East ordinate is 500'000.000 metres. The Southern part of each Zone has a North ordinate at the equator of 10'000'000.000 metres. The same point on the equator in terms of the Northern hemisphere has a North Ordinate of 0.000 metres. So one can see that mixing coordinates from different zones makes no sense, although a line that straddles the equator in the same zone maybe calculated with some adjustment to the North ordinate.

More to follow ...
So I discovered one can friend another templated class like this:

1
2
3
4
5
6
7
8
9
10
11
enum class ZoneNS: unsigned short {North, South};
  
  template<int ZoneId, ZoneNS ZNS >
  class UTMGrid;
  
  template<int ZoneId, ZoneNS ZNS>
  class UTMGridDelta {
  friend class UTMGrid<ZoneId, ZNS>;
  private:

...


This avoids the limited scope of the templates. And the UTMGrid and UTMGridDelta et al. having the same template parameters allows the enforcement in the operators.

Now here's the rub: these 2 classes are now very strongly coupled, there are more classes to add, I am worried about the whole thing becoming monolithic. Menage'a'sept anyone? To be fair, it is not an all to all relationship, the friending will be in pairs. I thought about using a Mediator pattern, but I am not sure how that would look, it's the template parameters and the value of the members that are needed for the operator overloading, not that the class state has changed.

So, am I going about this the right way? Is there some other way?

Also, does it matter given there will be a finite and small number of pairings?

This is also only the beginning: there is much more stuff to do with coordinate transforms, and all the Angles, Distances, Corrections and Adjustments. Oh, and these coordinates are 2d at the moment, they need to be 3d rather soon.

Thanks in Advance :+)
Last edited on
I would take a step back and factor your idea into what you really need.
instead of 57 types or whatever, maybe you can discover that
-20 types don't allow add
-13 types don't allow multiply
... etc, maybe do a K-map or other logical grid to boil it down to say 5 or 10 real types that you need to create?

there is no way to stop every goof, regardless. If you allow subtraction and not addition, and you allow the user to negate and subtract....

perhaps a concept or something can be used instead of making a new type?
Last edited on
Check out the implementation of std::chrono::time_point and std::chrono::duration, which seem analogous to UTMGrid and UTMGridDelta.
Last edited on
Hi and thanks all for your replies and ideas :+)

@jonnin
... etc, maybe do a K-map or other logical grid to boil it down to say 5 or 10 real types that you need to create?


Mmm, it's not just the operations, it's the template parameters. So there might be a number of classes which have the same template parameters, but there are other aspects which I haven't mentioned yet:

1. The interface is limited to just the constructors;
2. There is no changing values once constructed, new types are created using converting constructors;
3. Most of the action is in the overloaded operators;
4. I need to disallow (delete the operator) where the template parameters differ.
5. I thought about inheritance of a strong type, but the operations differ - UTMGrid allows subtraction, disallows addition; while UTMGridDelta is the opposite

So a prime example is to disallow operations on coordinates from different Zones, the Zone is a template parameter.

jonnin wrote:
perhaps a concept or something can be used instead of making a new type?


Well I think at this stage it will help in reducing the amount of operator overloading.

@mbozzi

I will have to look at that, although it may take me some time to trawl through the STL code on my machine, unless I find some other descriptive blog, Cheers, I will get back to you once I have more info on that. :+)



I read Jonathon Boccara's blog:
https://www.fluentcpp.com/2016/12/08/strong-types-for-strong-interfaces/

He does strong types like this:
1
2
struct WidthParameter {};
using Width = NamedType<double, WidthParameter>;


But that is just a different way of creating lots of types, albeit 1 class with inheritance. It seems to me that would suffer the same problem of needing friend to pair classes.

Hell, maybe I forgot the basics: provide an accessor interface for the classes.





just thinking about this a second...
have you ever tried to do serious math in like pure python (there are alternative implementations that I am not speaking about right now)? Even something simple (in my case, it was a very simple CRC computation) can be 5 to 10 TIMES slower than when written in good C or C++. Why, when its just FPU or int cpu crunchwork? Because python, in its infinite wisdom, uses objects for numerical variables. These objects have some coolness (like big-ints for encryption) but are incredibly slow.

Whatever you do, the deep innards of this need to fall back into machine language FPU/CPU instructions that crunch the numbers efficiently, is the point. If you end up putting layers of junk on top of every add or multiply, its going to be very sluggish if you do any volume of crunching, turning instant into seconds and seconds into minutes... you have to ensure stuff works and is safe, I get it, but this is an area where a very small thing can become a major bottleneck, so proceed with that in the back of your mind at all times? Or, if you only do a few computations here and there, it may not matter at all.
Hi jonnin,

Thanks for your reply. :+)

I was hoping that:

1. There will be zero overhead for the template stuff, it will happen at compile time? That is, it will select which operators it can use at compile time?
2. I will use a library called GeoLib by Dr Charles F.F. Karney to do the actual geodetic stuff. This library is fast, on my previous i7 machine it could do 1 million Grid to Lat/Long in 1 sec on a single thread. I am also planning to do that on CUDA, my current i9 machine has 3,328 core in the GPU, so if I use 3,000 of them it would cut down that time a lot.

My past use of templates was restricted to basic usage. I have the book "C++ Templates, The Complete Guide" by Vandevoorde, Josuttis, and Gregor, which I definitely need to read much more.

But maybe someone can answer this question easily: I am unsure about the exact scope of template parameters? Even though the template parameters were mentioned in the primary template, I had to mention them again, every time for the operators. Before I had the friend code mentioned above, any mention of another class would result in errors, even when I forward declared the class. I am at work today, I need to try more things when I have more time. What about multiple primary templates:

1
2
3
4
5
6
7
8
9
10
11
enum class ZoneNS: unsigned short {North, South};
  
  template<int ZoneId, ZoneNS ZNS >
  class UTMGrid;
  
  template<int ZoneId, ZoneNS ZNS>
  class UTMGridDelta {
 // friend class UTMGrid<ZoneId, ZNS>;
  private:

...


This is the same as before, I commented out the friend declaration. Does this I mean I could refer to UTMGrid inside the UTMGridDelta class, provided that I also have an accessor interface? I should try that. If that works, I could remove all the friends declarations.

I need to post some code, hopefully this evening, another 8 or 9 hours away.

1) this is true. That isn't the problem at all. Imagine a couple of addition operators:
{ return x+y;}
vs
{ if(someslowassfunction()) return x+y; else return x-y; }

the first one is going to run faster than the second one. Possibly by orders of magnitude, depending on what all is being done...

2) whether you need friends or not is just a design detail. I am not the best person to ask because my personal opinions lie in the 'make everything public and stop screwing it up' direction far more than the 'tie yourself in a knot making sure only those who need it can ever see/touch it' direction. So if you ask me, I will just say to make it public already. I know the arguments and even agree with them, but .. its complicated, and I have my reasons.
Last edited on
I read Jonathon Boccara's blog

That article is from 2016, C++20's constraints and concepts might make restricting what is doable with a template easier.

Not that I'd have a clue how to do it myself. ¯\_(ツ)_/¯
Registered users can post here. Sign in or register to post.