Proper use of a templated function


MPD78
09-08-2009, 10:13 AM
Hello all,

The program below will calculate the thermal conductivity of a gas mixture. The calculation for this uses the NR3 poly.h header. It only uses the first function in the struct Poly.

I wanted to get some experience with templated functions outside of the templated functions used in NR3, so I chose the calculation of a gas mixture's thermal conductivity to be my first templated function.

For the calculation, three variables have to be passed to the templated function. They are the temperature, the volume, and the gas component ID number. Temperature and volume are of type Doub and component ID number is of type Int.

My question is, am I using the templated function in an appropriate manner?

Here is the gasprop.h header file.

// This is the gasprop.h header file

#include "K:\Engineering Program Header Files\nr3.h"
#include "K:\Engineering Program Header Files\poly.h"

template<class T, class U, class X>
T k(T temp, U vol, X compnum)
{
// All temperatures are in input in degrees Farenheit. tk is returned in units of kCal/hr-m-C
if(compnum==1){ // N2 Equation
Doub coeff[] = {0.0135497,2.48754e-5,-2.7127e-9};
Int n=3;
VecDoub c(n,coeff);
tk1 = ((Poly(c)(temp))*1.488)*vol;}
if(compnum==2){ // O2 Equation
Doub coeff[] = {0.0135497,2.48754e-5,-2.7127e-9};
Int n=3;
VecDoub c(n,coeff);
tk2 = ((Poly(c)(temp))*1.488)*vol;}
if(compnum==3){ // CO2 Equation
Doub coeff[] = {7.54336e-3,2.83449e-5,-3.05653e-9};
Int n=3;
VecDoub c(n,coeff);
tk3 = ((Poly(c)(temp))*1.488)*vol;}
if(compnum==4){ // H2O Equation
Doub coeff[] = {0.0101497,2.37118e-5,2.51457e-9};
Int n=3;
VecDoub c(n,coeff);
tk4 = ((Poly(c)(temp))*1.488)*vol;}
if(compnum==5){ // SO2 Equation
Doub coeff[] = {4.67483e-3,9.95513e-6,-1.03438e-9};
Int n=3;
VecDoub c(n,coeff);
tk5 = ((Poly(c)(temp))*1.488)*vol;}
if(compnum==6){ // SO3 Equation
Doub coeff[] = {5.46413e-3,2.02404e-5};
Int n=2;
VecDoub c(n,coeff);
tk6 = ((Poly(c)(temp)*1.488))*vol;}
if(compnum==7){ // CL2 Equation
if(temp>=932)tk7=(((11.42+(temp-932)/180*0.67)*0.001)*1.488)*vol;
if (temp < 932) {
Doub coeff[] = {4.27922e-3,1.37061e-5,-6.5373e-9};
Int n=3;
VecDoub c(n,coeff);
tk7 = ((Poly(c)(temp))*1.488)*vol;}
}
if(compnum==8){ // AIR Equation
Doub coeff[] = {0.0135497,2.48754e-5,-2.7127e-9};
Int n=3;
VecDoub c(n,coeff);
tk8 = ((Poly(c)(temp))*1.488)*vol;}
if(compnum==9){ // H2 Equation
Doub coeff[] = {9.980769e-2,1.39971e-4,-1.64627e-8};
Int n=3;
VecDoub c(n,coeff);
tk9 = ((Poly(c)(temp))*1.488)*vol;}
if(compnum==10){ // CO Equation
Doub coeff[] = {1.28462e-2,2.01265e-5,-1.73368e-9};
Int n=3;
VecDoub c(n,coeff);
tk10= ((Poly(c)(temp))*1.488)*vol;}
if(compnum==11){ // NO Equation
Doub coeff[] = {0.018676,3.76459e-5,-3.48876e-9};
Int n=3;
VecDoub c(n,coeff);
tk11= ((Poly(c)(temp))*1.488)*vol;}
if(compnum==12){ // NO2 Equation
Doub coeff[] = {0.0111215,4.18116e-5,-4.49735e-9};
Int n=3;
VecDoub c(n,coeff);
tk12= ((Poly(c)(temp))*1.488)*vol;}
if(compnum==13){ // NH3 Equation
Doub t1 = temp+460;
tk13=(((8.01/1e6)*(pow(t1,1.19)))*1.488)*vol;}
if(compnum==14){ // CH4 Equation
Doub coeff[] = {1.64636e-2,5.35636e-5};
Int n=2;
VecDoub c(n,coeff);
tk14= ((Poly(c)(temp))*1.488)*vol;}
if(compnum==15){ // C2H6 Equation
Doub coeff[] = {9.25714e-3,4.11786e-5};
Int n=2;
VecDoub c(n,coeff);
tk15= ((Poly(c)(temp))*1.488)*vol;}
if(compnum==16){ // C3H8 Equation
Doub coeff[] = {7.48214e-3,3.55952e-5};
Int n=2;
VecDoub c(n,coeff);
tk16= ((Poly(c)(temp))*1.488)*vol;}
if(compnum==17){ // C4H10 Equation
Doub coeff[] = {6.67857e-3,2.97143e-5};
Int n=2;
VecDoub c(n,coeff);
tk17= ((Poly(c)(temp))*1.488)*vol;}
if(compnum==18){ // C5H12 Equation
Doub coeff[] = {6.56786e-3,2.8488e-5};
Int n=2;
VecDoub c(n,coeff);
tk18= ((Poly(c)(temp))*1.488)*vol;}
if(compnum==19){ // HCL Equation
Doub coeff[] = {6.91627e-3,1.49344e-5};
Int n=2;
VecDoub c(n,coeff);
tk19= ((Poly(c)(temp))*1.488)*vol;}
if(compnum==20){ // H2S Equation
Doub coeff[] = {9.1555e-3,1.87986e-5};
Int n=2;
VecDoub c(n,coeff);
tk20= ((Poly(c)(temp))*1.488)*vol;}

return tk=tk1+tk2+tk3+tk4+tk5+tk6+tk7+tk8+tk9+tk10+tk11+t k12+tk13+tk14+tk15+tk16+tk17+tk18+tk19+tk20;
}

// String fucntion for the component name vs. component ID number.
string compname(Int compnum){
if (compnum==1){return("N2 ");}
if (compnum==2){return("O2 ");}
if (compnum==3){return("CO2 ");}
if (compnum==4){return("H2O ");}
if (compnum==5){return("SO2 ");}
if (compnum==6){return("SO3 ");}
if (compnum==7){return("CL2 ");}
if (compnum==8){return("AIR ");}
if (compnum==9){return("H2 ");}
if (compnum==10){return("CO ");}
if (compnum==11){return("NO ");}
if (compnum==12){return("NO2 ");}
if (compnum==13){return("NH3 ");}
if (compnum==14){return("CH4 ");}
if (compnum==15){return("C2H6 ");}
if (compnum==16){return("C3H8 ");}
if (compnum==17){return("C4H10 ");}
if (compnum==18){return("C5H12 ");}
if (compnum==19){return("HCL ");}
if (compnum==20){return("H2S ");}
}

Here is the function needed to print the gas list.

void print_std_gases(Int compnum)
{
cout << "Below are the Standard Gases" << endl;
cout << endl;
cout << "1. N2" << setw(10) << "8. AIR" << setw(10) << "14. CH4" << setw(10) << "19. HCL" << endl;
cout << "2. O2" << setw(9) << "9. H2" << setw(12) << "15. C2H6" << setw(9) << "20. H2S" << endl;
cout << "3. CO2" << setw(8) << "10. CO" << setw(12) << "16. C3H8" << setw(9) << "" << endl;
cout << "4. H2O" << setw(8) << "11. NO" << setw(13) << "17. C4H10" << setw(8) << "" <<endl;
cout << "5. SO2" << setw(9) << "12. NO2" << setw(12) << "18. C5H12" << setw(8) << "" << endl;
cout << "6. SO3" << setw(9) << "13. NH3" << setw(12) << "" << setw(8) << "" << endl;
cout << "7. CL2" << setw(9) << "" << setw(12) << "" << setw(8) << "" << endl;

}

Here is my main() function.

// Main function

#include "K:\Engineering Program Header Files\nr3.h"
#include "K:\Engineering Program Header Files\print.h"
#include "gasprop.h"

// Declare the needed variables
Int pause,j,count; // dummy variables used for looping, counting, and keeping the output screen visible
Int compnum; // gas component number selected by the user
Int pf[20]; // array for the gas components
Int sfnum[20]; // array for the gas component I.D. numbers

Doub temp; // temperature passed to the template function
Doub vol; // volume percent of the gas entered by the user
Doub total; // total volume percent used to stop the looping and for checking if vol > 100.0
Doub pv[20]; // array for the gas volumes
Doub volumearray[20]; // array for the gas component volumes
Doub thermcond; // calculated thermal conductivity
Doub T1P; // user input temperature (F)
Doub tk,tk1,tk2,tk3,tk4,tk5,tk6,tk7,tk8,tk9,tk10; // thermal conduvtivity for each gas
Doub tk11,tk12,tk13,tk14,tk15,tk16,tk17,tk18,tk19,tk20; // thermal conductivity for each gas

string sfarray[20]; // array for the gas component names

char szInput[256]; // char variable used to obtain the correct input from the user

// Begin Main function
int main()
{
// Enter the gas mixture
cout << endl;
cout << "GAS COMPOSITION" << endl;
cout << "=============================================" << endl;
print_std_gases(compnum); // Print the standard gases. This function is located at K:\Engineering Program Header Files\print.h
count = j = 0; // Initialize counters and array index to zero
do {
do
{
cout << endl;
printf("Select gas component ");
gets(szInput);
compnum = atoi(szInput);
if (compnum==00) exit(1);
if (compnum < 1 || compnum > 20)
{
cout << "Gas not available " << endl;
}
}while(compnum < 1 || compnum > 20);
cout << endl << compname(compnum) << endl;
printf("Enter the volume percent ");
gets(szInput);
vol = atof(szInput);
vol = vol/100;
total += vol;
if (total > 1){
cout << "Total volume > 100% You must re-enter your values" << endl;
total = count = j = 0;} // Re-set the counter and total to zero
cout << "Total volume " << (total*100) << endl;
volumearray[j]=vol; // Send volume to array
sfarray[j]=compname(compnum); // Convert component ID # to component name and send to array
sfnum[j]=compnum; // Send component ID # to array
j++;
count++;
}while (total < 1 && total >= 0);

// Get user input temperature in degrees F.
printf("Enter the temperature (F) ");
gets(szInput);
T1P = atof(szInput);

// Begin calculation of thermal conductivity. Input temperature must be in degrees F.
temp = T1P;
for(Int j=0;j<count;j++)
{
vol = volumearray[j];
compnum=sfnum[j];
thermcond=k<Doub,Doub,Int> (temp,vol,compnum); // Calculate the thermal conductivity of the gas mixture
}

// Send results to output screen
cout << endl;
cout << "The thermal conductivity is" << setw(10) << setprecision(5) << thermcond << " kCal/hr-m-C" << endl;
cin >> pause; // Dummy input to keep the output screen visible

// End Main function
return 0;
}


The program compilies and produces the correct results.

Thanks to anyone who takes the time to answer this post.

Also, use this code at your own risk.

Thanks
Matt

davekw7x
09-09-2009, 09:21 AM
...
I wanted to get some experience with templated functionsThat's OK.
There is nothing wrong with experimenting with creation and use of templated functions, however...
...so I chose the calculation of a gas mixture's thermal... I'm not sure why. I mean, why would you need different functions named "k" with the exact same operational functionality but with different types for the arguments? I mean, you can make a templated function out of just about anything, but I don't really see the point (other than pedagogic) when a "regular" function would meet your needs.



My question is, am I using the templated function in an appropriate manner?

The following is not "wrong" by the rules of C++, but in my opinion it is a bit much:

thermcond=k<Doub,Doub,Int> (temp,vol,compnum);


Here's how I would expect to use that function:

thermcond=k(temp,vol,compnum);


When the compiler this statement, it looks everywhere to see if there is a function named k that takes arguments of the types that are consistent with the types that have been declared for temp, vol, and compnum. If we have a "regular" function named k that is known to the compiler with these types, it would use it. If we don't have a "regular" function named k with those types but we do have a templated function that can build a function with these types it uses the templated function.

If we have both a "regular" function and a templated function and we wanted to use the templated function, then we would have to use the notation of your example. But why would we do such a thing? See Footnote. The compiler always does exactly what we tell it (assuming we have created valid code), so the compiler doesn't get confused. It's a different story for people who have to validate and maintain the code. Why create unnecessary confusion for us mere mortals?

Naming is an extremely important part of good program design. In a small one-off program, call it what you want, but in a significant program that will be maintained, use of more descriptive variable names, and especially function names is important. If the function calculates thermal conductivity, why not give it a name that indicates the functionality? I mean, in the context of your project, maybe "k" is completely descriptive, and if you are going to throw the away after getting the answer that you expect, then call it k() if you want to, but I think that calling it something like "thermal_condictivity()" or some such thing would be better. (Don't use the name that I suggest, but use something that tells us what it does.)



Regards,

Dave

Footnote:

The program compiles and produces the correct results.

That's a Good Thing alright, but I think there should be more to the story. I mean, if there were only one way to do things like this (or like anything), life would be simple: Discover the Right Way. Do The Right Thing. Quit.

However...
You didn't ask for a critique of the overall program design or implementation, but I have to say something.

You mentioned that you hate the use of common blocks in Fortran programs. I won't go so far as to use the word "hate" in regards the gratuitous use of global variables in C and C++ programs, but I will say that I frown upon such things (as do all of the real C and C++ programmers that I have run across through the years).

I think it is particularly bad program design to use global or other static variables in a function like your k(). Why not just have the function return the value for the particular compnum with with it is supplied:


//template < class T, class U, class X > T k(T temp, U vol, X compnum)
// Non-templated function that does the same thing
Doub k(DoubT temp, DoubU vol, Int compnum)
{
// All temperatures are in input in degrees Farenheit. tk is returned in units of kCal/hr-m-C
if (compnum == 1) { // N2 Equation
Doub coeff[] = { 0.0135497, 2.48754e-5, -2.7127e-9 };
Int n = 3;
VecDoub c(n, coeff);
return ((Poly(c) (temp)) * 1.488) * vol;
}
if (compnum == 2) { // O2 Equation
Doub coeff[] = { 0.0135497, 2.48754e-5, -2.7127e-9 };
Int n = 3;
VecDoub c(n, coeff);
return ((Poly(c) (temp)) * 1.488) * vol;
}
if (compnum == 3) { // CO2 Equation
Doub coeff[] = { 7.54336e-3, 2.83449e-5, -3.05653e-9 };
.
. // Etc.
. // Doesn't require global variables tk, tk1, tk3, etc.
.
.
.
}


Then the main loop could be something like the following:


temp = T1P;
thermcond = 0.0;
for (Int j = 0; j < kount; j++) {
vol = volumearray[j];
compnum = sfnum[j];
thermcond += k(temp, vol, compnum); // Calculate the thermal conductivity of the gas mixture
}



The point I would like to make is that generally accepted "good programming practices" lead to creation of a function that exactly one thing: Calculates a particular value of conductivity for a particular gas. The main loop accumulates the values.

MPD78
09-09-2009, 09:46 AM
Dave, thanks for the critique. Very much appreciated.

As for this,

thermcond=k<Doub,Doub,Int> (temp,vol,compnum);


That came from my reference book.

Anyways, I will use the += operator to clean up the k()'s and switch over to a non-templated function. I agree, I should keep the programming simple when simplicity is available.

Thanks
Matt

davekw7x
09-09-2009, 09:55 AM
...critique...
My opinions are just that: opinions. Food for thought (I hope). Look at other examples by other programmers. Adopt the practices that make sense to you (and whoever it is that signs your paycheck).

I said:
I mean, if there were only one way to do things like this (or like anything), life would be simple

One other thing: Life would be even more simple if we were born knowing this stuff, right?


Regards,

Dave