Link to home
Start Free TrialLog in
Avatar of phoffric
phoffric

asked on

C++03 Templatize Type Safety Enforcement - Part 2

I have a short program shown below that is also located in:
https://www.experts-exchange.com/questions/29056211/C-03-Type-Conversion-Errors-for-primitive-types-Part-1.html?anchorAnswerId=42295035#a42295035

I would like to templatize it to reduce the amount of tedium in handling multiple type conversions. I only show three types here (Hz, KHz, MHz), but I will be adding GHz as well. To make the classes easier to create, I got this good tip on the beginnings of templatizing the type enforcement/conversion classes:

What I would like to do is be able to handle the below program which has a bunch of friends and multiple implicit conversions for each type.

If I can get some meta-programming technique (or just plain templatizing) to generate lots of this type of code, I would greatly appreciate seeing how to do this. I'll be having to deal with many other families of types, such as distance: (feet, inches, millimeters, meters); time(seconds, hours - yes, I know about chrono); speed, etc.
#include <iostream>

class Hz
{
	friend class KHz;
	friend class MHz;
public:
	explicit Hz(double val) : val_(val) {}
	Hz(const KHz & khz);
	Hz(const MHz & mhz);
	double operator()() { return val_; }

private:
	double val_;
};

class KHz
{
	friend class Hz;
	friend class MHz;
public:
	explicit KHz(double val) : val_(val) {}
	KHz(const Hz & hz) : val_(hz.val_ / 1000.) {}
	KHz(const MHz & mhz);
	double operator()() { return val_; }

	KHz operator*(double mult)
	{
		return KHz(val_ * mult);
	}

private:
	double val_;
};

class MHz
{
	friend class Hz;
	friend class KHz;
public:
	explicit MHz(double val) : val_(val) {}
	MHz(const Hz & hz) : val_(hz.val_ / 1.0e6) {}
	MHz(const KHz & khz) : val_(khz.val_ / 1.0e3) {}
	double operator()() { return val_; }

	KHz operator*(double mult)
	{
		return KHz(val_ * mult);
	}

private:
	double val_;
};

Hz::Hz(const KHz & khz) : val_(khz.val_ * 1000) {}
Hz::Hz(const MHz & mhz) : val_(mhz.val_ * 1e6) {}
KHz::KHz(const MHz & mhz) : val_(mhz.val_ * 1000.) {}


KHz doSomething(KHz khz)
{
	return khz * 2.0;
}


int main()
{
	Hz  hz = Hz(10000);  // Why doesn't Hz hz = 10000 compile?
	Hz  hz2 = Hz(1234.567);
	Hz  hz3(21300.0);	  // cleaner looking IMO
	KHz valkhz = KHz(4.0);
	KHz khz3(44.123);
	MHz mhz(3.2);
	MHz mhz2(4.2211);

	// It appears not to be necessary to define operator=. Why is that? When is = op necessary in this context?
	hz = valkhz;
	khz3 = hz3;

	valkhz = doSomething(hz2); // ok

	valkhz = KHz(43.99);
	hz2 = doSomething(valkhz); // OK, accepts KHz input and converts output to Hz

	double shouldBeKHz = 3.0;
	valkhz = doSomething(KHz(shouldBeKHz)); // OK

	mhz2 = khz3 = hz3;  // multiple implicit conversions
	valkhz = doSomething(mhz2);

	std::cout << hz3() << " " << khz3() << " " << mhz2() << " " << valkhz() << std::endl;
}

Open in new window


Here is the template code that evilrix suggested:
enum ConvFactor{
   HZ  =    1,
   KHZ =   1000,
   MHZ =  1000000
};

template <ConvFactor CF>
class Frequency
{
public:
   explicit Frequency(double val)
      : val_(val / ConvFactor)
   {
   }

   template<ConvFactor LocalCF>
   explicit Frequency(Frequency<LocalCF> const & freq)
      : val_((freq.val_ * LocalCF) / CF)
   {
   }

private:
   double val_;
};

typedef Frequency<HZ>   Hz;
typedef Frequency<KHZ> KHz;
typedef Frequency<MHZ> MHz;

Open in new window

https://www.experts-exchange.com/questions/29056211/C-03-Type-Conversion-Errors-for-primitive-types-Part-1.html?anchorAnswerId=42295757#a42295757
Avatar of phoffric
phoffric

ASKER

The tricky part looks like trying to get multiple conversion constructors into the template.
I'm going to be honest, I'm not quite sure what it is you need here. My original code should already handle implicit conversion between the various frequency types. Can you clarify you requirement, please? :)
My top level goal is to get line 91 in the above code to successfully print out and to learn how to use templates to make it easier to write all those classes and constructors necessary to produce functionally equivalent code in the OP. The OP code works without templates and establishes a good goal for both POD type safety as well as allowing implicit conversions between the chosen classes of a family of types. I'll be creating other families besides frequency, such as distance, speed, energy units (like watts, etc.), and more. Later, it will get a little trickier when I combine user types of different families - for example, combining foot and pounds, or newtons and meters. But I'll worry about that later. Right now, I have made a good step forward without having boost or C++ at my disposal.

I didn't understand all the details so I asked this question since I received a good answer in that question, and now I am just following up to see whether I can use templates to be able to run the code from lines 60-92.

If by using templates, I may need to modify those lines, but still have the same effect, that would also be pretty good.

Some questions about the template:
1. ConvFactor LocalCF - How does the compiler knows what LocalCF is?
2. Since my classes have 3 constructors in them (and 4 if I add GHz), I wasn't sure how the template got by with only two constructors.
3. When I applied the template, some compiler errors were for lines 77, 78, etc. By stepping into my original non-template program, I saw that the conversion constructors were called, and somehow the assignment operator was not needed. (Smart conversion constructors, I guess.)
4. I assume that line 12 should replace ConvFactor with CF. After doing that, I still cannot get the program to  compile.

Perhaps you could give an example of usage as applicable to my example program.

Thanks,
Paul
ASKER CERTIFIED SOLUTION
Avatar of evilrix
evilrix
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Your program works, and I'll be digesting what you said especially w.r.t.  LocalCF. For someone who said they are not good with numbers...
: val_((freq.Val() * LocalCF) / CF)

Open in new window

looks pretty darn good. :)

I tried to get the doSomething() function added, but having trouble. I also may have other questions about best practices (explicit vs implicit) for another question - I need to think about this a bit more.

Could you help explain what is wrong with this w.r.t. dosomething() and the * operator?
// #define NOWORKY

#include <iostream>


enum ConvFactor {
	HZ = 1,
	KHZ = 1000,
	MHZ = 1000000,
	GHZ = 1000000000
};

template <ConvFactor CF>
class Frequency
{
public:
	explicit Frequency(double val)
		: val_(val / CF)
	{
		std::cout << "  CF: " << CF << std::endl;
	}

	template<ConvFactor LocalCF>
	explicit Frequency(Frequency<LocalCF> const & freq)
		: val_((freq.Val() * LocalCF) / CF)
	{
		std::cout << "LocalCF: " << LocalCF << "  CF: " << CF << std::endl;
	}

#ifndef NOWORKY
	template<ConvFactor CF>
	CF operator*(double mult)
	{
		return CF(val_ * mult);
	}
#endif

	double Val() const { return val_; }
	double operator()() { return val_; }

private:
	double val_;
};

typedef Frequency<HZ>   Hz;
typedef Frequency<KHZ> KHz;
typedef Frequency<MHZ> MHz;
typedef Frequency<GHZ> GHz;



#ifndef NOWORKY
template<ConvFactor CF>
CF doSomething(CF khz)
{
	return khz * 2.0;
}
#endif

int main()
{
	Hz hz = Hz(1000);

	KHz khz = KHz(hz);

	MHz mhz1 = MHz(hz);
	MHz mhz2 = MHz(khz);

	GHz ghz1 = GHz(hz);
	GHz ghz2 = GHz(khz);
	GHz ghz3 = GHz(mhz1);
	GHz ghz4 = GHz(mhz2);

#ifndef NOWORKY
	khz = doSomething(mhz2);
#endif

	std::cout << hz() << " " << khz() << " " << mhz2() << " " << " " << ghz1() << std::endl;
}

Open in new window

SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Ideally, a const reference of Frequency, for efficiency.
Current program. If less errors is better, this gives only a 2 errors shown after program.
Note: If I do not reference doSomething in lines 66 and 77, then no compiler errors.
I tried other variations, but got more than 2 errors.
#include <iostream>


enum ConvFactor {
	HZ = 1,
	KHZ = 1000,
	MHZ = 1000000,
	GHZ = 1000000000
};

template <ConvFactor CF>
class Frequency
{
public:
	explicit Frequency(double val)
		: val_(val / CF)
	{
		std::cout << "  CF: " << CF << std::endl;
	}

	template<ConvFactor LocalCF>
	explicit Frequency(Frequency<LocalCF> const & freq)
		: val_((freq.Val() * LocalCF) / CF)
	{
		std::cout << "LocalCF: " << LocalCF << "  CF: " << CF << std::endl;
	}

	template<ConvFactor CF>
	Frequency<CF> operator*(double mult)
	{
		return Frequency<CF>(val_ * mult);
	}

	double Val() const { return val_; }
	double operator()() { return val_; }

private:
	double val_;
};

typedef Frequency<HZ>   Hz;
typedef Frequency<KHZ> KHz;
typedef Frequency<MHZ> MHz;
typedef Frequency<GHZ> GHz;



template<ConvFactor CF>
Frequency<CF> doSomething(Frequency<CF> freq)
{
//	return Frequency<CF>(freq * 2.0);
	return (freq * 2.0);
}

//KHz doSomething2(KHz khz)
//{
//	return khz *<KHZ> 2.0;
//}

int main()
{
	Hz hz = Hz(1000);

	KHz khz = KHz(hz);

	khz = KHz( doSomething<KHZ>(khz) );
//	khz = doSomething2(khz);

	MHz mhz1 = MHz(hz);
	MHz mhz2 = MHz(khz);

	GHz ghz1 = GHz(hz);
	GHz ghz2 = GHz(khz);
	GHz ghz3 = GHz(mhz1);
	GHz ghz4 = GHz(mhz2);

	khz = doSomething(KHz(mhz2));

	std::cout << hz() << " " << khz() << " " << mhz2() << " " << " " << ghz1() << std::endl;
}

Open in new window

line 52:   return (freq * 2.0);
Error      C2783      
'Frequency<LocalCF> Frequency<KHZ>::operator *(double)': could not deduce template argument for 'CF'

Error      C2676      
binary '*': 'Frequency<KHZ>' does not define this operator or a conversion to a type acceptable to the predefined operator
https://ideone.com/aDD1iR

 I commented out line 28 and I see I got some results which I think are correct.

Digesting your explanation I believe that one typedefs will instantiate the class ( or maybe when they typedef alias is actually used the class template that's instantiated ) but the template of local CF maybe instantiated zero to four times within one class. Does that sound right to you?

 Is there a way to see the final results of the instantiated code ?
Very interesting approach. Thanks! Learned much.
Does this help?

#include <iostream>


enum ConvFactor {
   HZ = 1,
   KHZ = 1000,
   MHZ = 1000000,
   GHZ = 1000000000
};

template <ConvFactor CF>
class Frequency
{
public:
   explicit Frequency(double val)
      : val_(val / CF)
   {
      std::cout << "  CF: " << CF << std::endl;
   }

   template<ConvFactor LocalCF>
   explicit Frequency(Frequency<LocalCF> const & freq)
      : val_((freq.Val() * LocalCF) / CF)
   {
      std::cout << "LocalCF: " << LocalCF << "  CF: " << CF << std::endl;
   }

   double Val() const { return val_; }
   double operator()() { return val_; }

private:
   double val_;
};

template<ConvFactor CF>
Frequency<CF> operator*(Frequency<CF> freq, double mult)
{
   return Frequency<CF>(freq.Val() * mult);
}

template<ConvFactor CF>
Frequency<CF> operator*(double mult, Frequency<CF> freq)
{
   return Frequency<CF>(freq.Val() * mult);
}

typedef Frequency<HZ>   Hz;
typedef Frequency<KHZ> KHz;
typedef Frequency<MHZ> MHz;
typedef Frequency<GHZ> GHz;



template<ConvFactor CF>
Frequency<CF> doSomething(Frequency<CF> freq)
{
   return freq * 2.0;
}

int main()
{
   Hz hz = Hz(1000);

   KHz khz = doSomething(KHz(2));

   MHz mhz1 = MHz(hz);
   MHz mhz2 = MHz(khz);

   GHz ghz1 = GHz(hz);
   GHz ghz2 = GHz(khz);
   GHz ghz3 = GHz(mhz1);
   GHz ghz4 = GHz(mhz2);

   khz = doSomething(KHz(mhz2));

   std::cout << hz() << " " << khz() << " " << mhz2() << " " << " " << ghz1() << std::endl;
}

Open in new window


Note, for overloaded binary operators you are normally better off declaring them as free-standing functions so as to be able to support the correct semantics of (as in this case) X * Y and Y * X. Using a class member overloaded operator means you can only support one variant (X * Y) due to the way operator semantics work.
Yes, I knew that part; but before I did that I wanted to understand what was going on with the template within the template . I think you explained it really well and I appreciate that . Thanks again . I also believe that I figured out the implicit explicit issues . I looked at a boost units video and I see that there is a ongoing debate about which is better . And the conclusion of the speaker was that it is a matter of style. Wouldn't be surprised if I run into more problems, if so I'll ask more questions - thanks .

 One thing that surprises me is that from the boost units video , I am unclear what benefits there is to boost units over what you have done. Of course this is naïve as I  really haven't started using this template yet. one nice thing about boost is when printing out your values it also prints out by default the dimensions and that certainly would help in debugging. But in terms of ugliness of code , boost does not look that nice . Your version looks quite elegant . Did you just make that up with this question? Anyway , thanks to your explanations, I think I can get the dimensions printed out automatically also .
Hmm, I will post another question later.
>> the conclusion of the speaker was that it is a matter of style
Do you meant whether to use explicit on a constructor or not? It's not a matter of style, it's a matter of semantics and whether you wish to allow or disallow implicit cast semantics. For example:

class foo{ public: foo(bool x) {} };

This class will perform an implicit conversion from int to bool. That may or may not make sense. If you make the constructor explicit then it will not perform this conversion implicitly.

class foo{ public: foo(bool x) {} };
class bar{ public: explicit bar(bool x) {} };

int main()
{
   foo myFoo = 11; // allowed
//   bar myBar1 = 99; // not allowed, explicit constructor
   bar myBar2(87);  // allowed, explicit construction
}

Open in new window


So, whether you use explicit or not isn't a matter of style, it's a matter of making sure your code does what it's supposed to. As a rule of thumb, constructors that take a single argument are normally considered candidates for explicit and if you check the STL you'll see that explicit it used quite a lot.

Another reason to use explicit is to ensure expensive conversions (that would make sense, but are too costly to allow) are avoided. For example, you might have a constructor that takes a const reference to a std::string. Allowing it to perform an implicit conversion from char const * would be semantically sane, but as this would involve the construction of a temporary std::string, this may not be acceptable in your code. Using explicit will avoid this scenario.

>> I am unclear what benefits there is to boost units over what you have done
Well, I guess Boost Units is a tried and tested solution that is pre-packaged in a library and is well documented :)

>> Did you just make that up with this question?
Yeah, but as I write code like this all the time it wasn't particularly hard. Putting it another way, I know you are very good at math and I'm sure something I'd struggle with in math would be something you could just do off the top of your head. It just so happens that I spend most of my time writing low-level code that has to be very generic and so I make a lot of use of C++ templates.

If you want to get more into meta-template coding I strongly recommend these books:

- http://www.tmplbook.com/
- http://erdani.com/index.php/books/modern-c-design/

They are a bit pricey, but worth their weight in gold!
i tried a solution by using inheritance rather than templates.

class HzBase
{
private:
    double val_;
    int    fct_;
protected:
	explicit HzBase(double val, int fct)  : val_(val), fct_(fct) {}
public:
    double Double() const { return val_; } 
    int    Factor() const { return fct_; } 

    double operator()() const { return val_; }

    HzBase operator+(const HzBase & hzb) const { return (HzBase((Double()+((hzb.Double()*hzb.Factor())/ Factor())), Factor())); }
    friend HzBase operator*(const HzBase & hzb, double d)  { return HzBase(hzb.Double()*d, hzb.Factor()); } 
    friend HzBase operator*(double d, const HzBase & hzb)  { return HzBase(hzb.Double()*d, hzb.Factor()); } 
};

class Hz : public HzBase
{
public:
    explicit Hz(double val) : HzBase(val, 1) {}
    Hz(const HzBase & hzb) : HzBase(hzb.Double()*hzb.Factor(), 1)   {}
};

class KHz : public HzBase
{
    enum {CF = 1000};
public:
    explicit KHz(double val) : HzBase(val, CF) {}
    KHz(const HzBase & hzb) : HzBase((hzb.Double()*hzb.Factor())/CF, CF)   {}
};

class MHz : public HzBase
{
    enum {CF = 1000000};
public:
    explicit MHz(double val) : HzBase(val, CF) {}
    MHz(const HzBase & hzb) : HzBase((hzb.Double()*hzb.Factor())/CF, CF)   {}
};

class GHz : public HzBase
{
    enum {CF = 1000000000};
public:
    explicit GHz(double val) : HzBase(val, CF) {}
    GHz(const HzBase & hzb) : HzBase((hzb.Double()/hzb.Factor())/CF, CF)   {}
};

KHz doSomething(const KHz & khz, const Hz & hm)  
{ 
    return KHz(khz + hm); 
}


int main()
{
   Hz hz = Hz(1000);

   KHz khz = doSomething(KHz(2), Hz(4321));

   MHz mhz1 = MHz(hz);
   MHz mhz2 = MHz(khz);

   GHz ghz1 = GHz(hz);
   GHz ghz2 = GHz(khz);
   GHz ghz3 = GHz(mhz1);
   GHz ghz4 = GHz(mhz2);

   khz = doSomething(KHz(mhz2), Hz(mhz1));

   std::cout << hz() << " " << khz() << " " << mhz2() << " " << " " << ghz1() << std::endl;

Open in new window


Sara
Will respond to https:#a42298161 when I get home.

Sara, recall my comment in my previous question. I think it went like "oh groan" - expresses sentiment in dealing with many families of units. The meta-programming technique simplifies the developers experience. It is almost like one size fits all. Maybe the "almost" can be removed. With the meta-programming technique, it looks to me that I can have developers, who may not even be familiar with templates, be able to follow a simple recipe in creating their own units and be able to get rolling fast with code that generates itself. :)

Again, thanks for the intro to meta-programming, Ricky. Our systems engineer and PM are very interested in pursuing this. The systems engineer prefers explicit conversions rather than implicit.

To repeat - what I don't understand is why the above code in main() looks much easier to follow than the ugly boost units required type-casting. I am guessing that boost units has some features that I have not considered yet, but may reveal itself as I get more involved.

Other dimensional analysis areas that I am leaning towards:
1. Make the class name part of the template
2. Make the set of enumeration values part of the template so that they are not universally available
3. Be able to operate on two different families of units. Obvious example would be "distance (meters) = rate (meters/sec) * time (sec)".
This type of problem is exactly what templates and generic programming are all about. You get the compiler to write all the code for you with a simple typedef. Doing this using runtime polymorphism has a number of disadvantages:

1. You end up writing a lot more code (consider - typedef vs having to implement a concrete class)
2. Checks have to be performed at runtime and handled, whereas templates ensure everything is checked at compile time
3. Runtime is more expensive as you have to deal with virtual functions to gain polymorphic behaviour
4. You can't specialise behaviour without sub-classing and you can't do that if functions aren't virtual - template can be specialised

>> much easier to follow than the ugly boost units
Well, Boost Units is a DSL (Domain Specific Language) used for implementing various unit types and, as such, has way more going on, is feature rich and has a lot more to account for. What I've shown you is specific to your needs and, thus, way simpler. For another example of just how ugly things can get, take a lot at C++11's chrono library. This is hugely ugly, but by necessity.
1. You end up writing a lot more code (consider - typedef vs having to implement a concrete class)
didn't count the numbers of lines but surely it is same order as the template code since the baseclass would implement most of functionality.

2. Checks have to be performed at runtime and handled, whereas templates ensure everything is checked at compile time
actually that is an advantage as it doesn't require to explicitly define and instantiate your types at compile time.

3. Runtime is more expensive as you have to deal with virtual functions to gain polymorphic behaviour
granted. but my sample doesn't include virtual functions.

4. You can't specialise behaviour without sub-classing and you can't do that if functions aren't virtual - template can be specialised
again, my sample doesn't include virtual functions. so all basic operators can be implemented in the baseclass.

when using public inheritance the main question is whether for any of the derived classes the question of the "is a" property could be answered with "yes". obviously, any of Hz, KHz, MHz, and GHz "IS A" frequency unit. so, the HzBase is much less away from a concrete type as the template class Frequency.

note, you asked for a template solution which was shown by Rix absolutely convincing. but templates have one main disadvantage: there is no genuine way to convert one template into another without explicit instantiation.

Sara
@evilrix
>> it's a matter of semantics and whether you wish to allow or disallow implicit cast semantics.
That's what the lecturer meant when he referred to style. He was talking about the choices to make when implementing the constructors.

This point is now moot, since
>> The systems engineer prefers explicit conversions rather than implicit.
Since he is the Subject Matter Expert, his preference is the gold. I'll be going explicit.

>> Another reason to use explicit is to ensure expensive conversions (that would make sense, but are too costly to allow) are avoided. For example,
Good point.

I agree with you on points 1 and 2. I'll visit Sara's comment this weekend on point 3 if not deluged with other work.
I didn't understand point 4.

C++ Templates - The Complete Guide, 2nd Edition  - Is 1st edition worth getting (dated 2002)?

@Sara,
>> but templates have one main disadvantage: there is no genuine way to convert one template into another without explicit instantiation.

The reduced code is due to not having to write many unit classes during the life-cycle of multiple projects over the years. IMO, the succinct recipe that evilrix showed is more ideal in getting developers to get it right the first time.
The reduced code
since for unit classes the baseclass can provide most or all functioniality, it is not significantly more code. you even could use template constructors - as shown by Rix - to avoid individual constructors.

the succinct recipe that evilrix showed is more ideal in getting developers to get it right the first time.
sorry, that is isn't a good argument since the technic used with the template solution is much more challenging and sophisticated than deriving from a baseclass and calling baseclass functions.

from a design point of view unit classes of the same kind surely do share more than a common implementation of methods and properties but they are really the same thing with an individual factor for each derivation. so, KHZ and HZ are much more related than KHZ and KM. and inheritance is a stronger relation than a common enum type used for templates. with inheritance you can convert between units in a similar way as we humans do. for example we easily calculate with both meters and centimeters if required and with a little practice we even do with miles and kilometers.

furthermore, the concepts do not mutually exclude themselves. you could implement the baseclass and the constructors by means of templates.

"oh groan"

can you explain what you mean with this? i didn't find the comment neither in this question nor in the one before.

Sara
>> but surely it is same order as the template code
Nope. Implementation of concrete class vs. a single typedef (and that's optional). Also, look at the follow up question for how this can be expanded to use Policy Concepts to allow for more flexibility.

>> actually that is an advantage as it doesn't require to explicitly define and instantiate your types at compile time.
That's right, the performance cost is shifted to runtime. How is that better? Also, so are the errors and handling of them. How is that better? It's not just my assertion that compile time errors are preferable to runtime, it's a common knowledge that build errors are preferable to runtime errors! The cost of identifying and fixing errors increases everytime an error moves down the development chain. The earlier you catch them the better and you can't get any earlier than during build time.

There's a reason static_assert was introduced in C++11, it's because detection of errors during the build process is far better than detecting them at runtime!

Don't take my word for it: https://www.safaribooksonline.com/library/view/c-coding-standards/0321113586/ch15.html

C++ Coding Standards: 101 Rules, Guidelines, and Best Practices
by Herb Sutter; Andrei Alexandrescu
Published by Addison-Wesley Professional, 2004

14. Prefer compile- and link-time errors to run-time errors
SUMMARY
Don’t put off ’til run time what you can do at build time: Prefer to write code that uses the compiler to check for invariants during compilation, instead of checking them at run time. Run-time checks are control- and data-dependent, which means you’ll seldom know whether they are exhaustive. In contrast, compile-time checking is not control- or data-dependent and typically offers higher degrees of confidence.

DISCUSSION
The C++ language offers many opportunities to “accelerate” error detection by pushing it to compilation time. Exploiting these static checking capabilities offers you many advantages, including the following:

• Static checks are data- and flow-independent: ..

>> granted. but my sample doesn't include virtual functions.
Granted, that's why I specifically said: "...to gain polymorphic behaviour"

In other words, if polymorphic behaviour is required then vitual functions will be needed. Static polymorphic behaviour doesn't have this cost.

>> Hz, KHz, MHz, and GHz "IS A" frequency unit
Paul is looking for a generic solution to handle different measuremeent types and different units, not just frequencies. This is easy to do with Template policies, but not so easy with runtime inheritence.

>> have one main disadvantage: there is no genuine way to convert one template into another without explicit instantiation.
This is because you are trying to mix generic code (static polymorphism) with inheritence code (dynamic polymorphism). Stick to one or the other and this isn't a problem. The problem here is that most C++ programmers tend to thing of templates as something used to donate types within the class, they don't understand how to nor ever use them as policies to implement static polymorphism (understandably, because this can be a tricky programming paradigm to follow and master).

>> The reduced code is due to not having to write many unit classes
Exactly! And this grows exponetially when you want to consider different measurement types, each of which has their own measurement units.

>> for unit classes the baseclass can provide most or all functioniality,
Yes, but the problem is that Paul wants to deal with different measurement types as well as units and so the code will grow exponentionally and at that point specialisation will almost certainly be needed.

>> the template solution is much more challenging
That's also not a good argument. Just because you don't understand a techique doesn't mean it's not a good solution, it just means you don't understand it. Yes, I agree that this is a little more tricky to learn, but that's only because it's a technique that is really specific to C++, whereas dynamic polymorphism is a paradigm used by all OOP languages.

>> and sophisticated than deriving from a baseclass and calling baseclass functions.
Yes, it is more sophisticated :)

>> from a design point of view unit classes of the same
Okay, so the std::chrono library only deals with time, yet it uses a similar technique over inheritence. Why do you think that is?

Sara, I do accept that working with templates beyond the common paradigm of containers with type T or functions that accept various types does require a more thorough understanding of the C++ template and type system, but that isn't a good reason to not use it. Policy driven templates is a really powerful technique that solves many problems that are hard (if not impossible) to solve using standard OOP. If Paul's requirements were just the simple example then I'd agree that either technique show here would probably suffice, but it's my understanding that Paul is looking to develop a framework for other engineers to be able to create types to handle different measurement types with different units. Trying to do that using standard OOP will become a massive chore. Using policies makes it way easier, it also makes it far more extensible by making use of policies and most errors will be detected at compile time, which means if code builds it'll almost certainly work (logical issues aside) and no additional code will be required to enforce type complience.

Anyway, I think we're done here - Sara, I'd love to see your input in the follow-up question as I always enjoy these discussions with you.
Implementation of concrete class vs. a single typedef
you should remain fair in objecting. one class definition with nothing more than 2 constructors calling the baseclass constructor is nothing what would make the code significantly bigger compared to template code.

you are trying to mix generic code (static polymorphism) with inheritence code (dynamic polymorphism).
can you show me where i do that?

Yes, but the problem is that Paul wants to deal with different measurement types as well as Units ... the code will grow exponentionally and at that point specialisation will almost certainly be needed
actually you could use template code for the baseclasses... exponential growing happens if you would try to have a separate class for any combination of unit types. that would happen when using template types while baseclasses can be used as arguments of functions.

the template solution is much more challenging
That's also not a good argument

my argument was a reply to Paul's comment where he said that the template recipe "is more ideal in getting developers to get it right the first time". it was not a general argument beside that simpler solutions generally would tend to be less error-prone and could be better maintained as a rule of thumb.

so the std::chrono library only deals with time, yet it uses a similar technique over inheritence. Why do you think that is?
actually i can say little to std::chrono since i didn't even know that it exists, but i don't think that a container class which probably is 20 years old real would help us to find out whether unit classes better should be designed over templates or over inheritance.

ok. let us go to the next question where i already detected 'unit families' which is exactly that what could be handled best by using inheritance (as already told by the name).

Sara
Chrono isn't a container class, it's a policy driven chronological framework, which was introduced in C.++11.

The problem here is that you are trying to argue against a programming idiom you don't seem to understand. Policy driven design is a powerful idiom. That it's not one you are familiar with means you are arguing from a position of ignorance. I do not mean that in an offensive way, I mean you just don't know enough about the idiom to constructively criticised it and are putting forward ideas that, whilst not bad in a technical sense, they are not optimal for the problem domain.

I'd strongly suggest doing some reading up on Policy based design. The links I gave, in one of my other comments) would serve to be a good starting point.

Anyway, thanks for the discussion.
actually it doesn't matter whether std::chrono is a new or an old class. since i didn't know it (and i never claimed to be an expert in c++11), i have a good right to to not accept it as an argument, especially in a question which still has a C++03 restriction in the title.

so, it is somewhat annoying that you drop some out-of-topic term into, and when i say that i don't know what you mean, you are stating that i am criticizing a concept where i don't have an idea of.  

yes, i don't have any idea of the policy design but what has that todo with my statement that inheritance is a good (perhaps a superior) concept to handle different units of the same kind  (Paul used the term families in his next thread). if the policy design is even better for the given task, you will have any time to prove that. you could start to implement a function similar to CalculateSpeed which i posted in the new thread. this function returns an object of the SpeedFamily and takes a DistanceFamily and a TimeFamily object.

note, while i have no idea of the policy design, i am very experienced by using templates (C++03). so i think i can judge very well for my tasks when it makes sense to using templates and when it is better to keeping my hands off. the decision when doing the one or the other might be different between us, but obviously there is no either-or. you will use inheritage and i will use templates if we feel it is right. surely i could choose the wrong design for some given problem but it is still open whether it might not be the right one for me where i feel comfortable with and which i can defend against criticism, if necessary.

Paul, sorry if we misuse that thread for this discussion. but as it already was finished we are not fighting for points.

Sara
>> you are stating that i am criticizing a concept where i don't have an idea of.  
I am simply saying that you appear to have no expertise in Policy driven design. No more and no less. I thought I made that clear in my original post. I think I also made it clear that this wasn't a criticism. You'd be equally correct in stating I am ignorant of anything more than basic level math, because I am - and I know you are not. In that area I wouldn't be able to, for example, criticise a Calculus solution in the maths domain as I have no more than a rudimentary understanding of it. That's all I was suggesting, that you may not be in a position to criticise Policy driven design if it's not a subject matter you know (very well). No offence was intended or implied.

>> especially in a question which still has a C++03 restriction in the title.
I wasn't suggesting the chrono library was useful to solve Paul's problem, I was just using this as an example of where Policies have been used in preference to inheritance, because the problem domain suits it.

>> it is somewhat annoying that you drop some out-of-topic term into
It's not really, as it's an example of Policy driven design... and, as such, is a good example of where it's been used and can been seen as a juxtaposition to the solution I've suggested to Paul.

Anyway, I think we've discussed this to a point where there's nothing more useful to add and so, again, I thank you for the discussion.