Issue #013 July, 1996 Contents: New Language Feature - Explicit Introduction to Templates Part 5 - Forcing Instantiation Using C++ as a Better C Part 13 - Character Types and Arrays A Long Example of a Complete Class NEW LANGUAGE FEATURE - EXPLICIT In C++ it is possible to declare constructors for a class, taking a single parameter, and use those constructors for doing type conversion. For example: class A { public: A(int); }; void f(A) {} void g() { A a1 = 37; A a2 = A(47); A a3(57); a1 = 67; f(77); } A declaration like: A a1 = 37; says to call the A(int) constructor to create an A object from the integer value. Such a constructor is called a "converting constructor". However, this type of implicit conversion can be confusing, and there is a way of disabling it, using a new keyword "explicit" in the constructor declaration: class A { public: explicit A(int); }; void f(A) {} void g() { A a1 = 37; // illegal A a2 = A(47); // OK A a3(57); // OK a1 = 67; // illegal f(77); // illegal } Using the explicit keyword, a constructor is declared to be "nonconverting", and explicit constructor syntax is required: class A { public: explicit A(int); }; void f(A) {} void g() { A a1 = A(37); A a2 = A(47); A a3(57); a1 = A(67); f(A(77)); } Note that an expression such as: A(47) is closely related to function-style casts supported by C++. For example: double d = 12.34; int i = int(d); INTRODUCTION TO TEMPLATES PART 5 - FORCING INSTANTIATION In previous issues we've talked about the process of template instantiation, in which template parameters are bound to actual type arguments. For example: template class A {}; A a; At instantiation time, the template formal parameter T is assigned the type value "int". Instantiation is done based on need -- the generated class A will not be instantiated unless it has first been referenced or otherwise used. The actual process of instantiation is done in various ways, for example during the link phase of producing an executable program. But it is possible to explicitly force instantiation to occur in a file. For example: template class A { T x; void f(); }; template void A::f() {} template class A; will force the instantiation of A. The whole area of instantiation is still in a state of flux, and this feature may not be available with your compiler. USING C++ AS A BETTER C PART 13 - CHARACTER TYPES AND ARRAYS There are a couple of differences in the way that ANSI C and C++ treat character constants and arrays of characters. One of these has to do with the type of a character constant. For example: #include int main() { printf("%d\n", sizeof('x')); return 0; } If this program is compiled as ANSI C, then the value printed will be sizeof(int), typically 2 on PCs and 4 on workstations. If the program is treated as C++, then the printed value will be sizeof(char), defined by the draft ANSI/ISO standard to be 1. So the type of a char constant in C is int, whereas the type in C++ is char. Note that it's possible to have sizeof(char) == sizeof(int) for a given machine architecture, though not very likely. Another difference is illustrated by this example: #include char buf[5] = "abcde"; int main() { printf("%s\n", buf); return 0; } This is legal C, but invalid C++. The string literal requires a trailing \0 terminator, and there is not enough room in the character array for it. This is valid C, but you access the resulting array at your own risk. Without the terminating null character, a function like printf() may not work correctly, and the program may not even terminate. A LONG EXAMPLE OF A COMPLETE CLASS As a means of illustrating what an actual large and complete C++ class looks like, we will present a class for managing calendar dates. Commentary on this class is given below the source. First of all, the header: // Date class header file #ifndef __DATE_H__ #define __DATE_H__ typedef unsigned short Drep; // internal storage format const int MIN_YEAR = 1875; const int MAX_YEAR = 2025; const Drep MAX_DAY = 55152; const int DOW_MIN = 6; class Date { Drep d; // actual date static int init_flag; // init flag static int isleap(int); // leap year? static Drep cdays[MAX_YEAR-MIN_YEAR+1]; // cumul days per yr static void init_date(); // initialize date static Drep mdy_to_d(int, int, int); // m/d/y --> day static void d_to_mdy(Drep,int&,int&,int&);// day --> m/d/y public: Date(Drep); // constructor from internal Date(const Date&); // copy constructor Date(int, int, int); // constructor from m/d/y Date(const char*); // constructor from char* operator Drep(); // conversion to Drep void print(char* = (char*)0); // print void get_mdy(int&, int&, int&); // get m/d/y long operator-(const Date&); // difference of dates int dow(); // day of week long wdays(const Date&); // work days between dates }; #endif and then the source itself, along with a driver program: // Date class and driver program #include #include #include #include #include #include "date.h" // days in the various months const char days_in_month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; Drep Date::cdays[MAX_YEAR - MIN_YEAR + 1]; int Date::init_flag = 0; // initialize date structures void Date::init_date() { int i; Drep cumul = 0; init_flag = 1; for (i = MIN_YEAR; i <= MAX_YEAR; i++) { cumul += 365 + isleap(i); cdays[i - MIN_YEAR] = cumul; } } // a leap year? int Date::isleap(int year) { if (year % 4) return 0; if (year % 100) return 1; if (year % 400) return 0; return 1; } // convert m/d/y to internal date Drep Date::mdy_to_d(int month, int day, int year) { int i; Drep d; assert(month >= 1 && month <= 12); assert(day >= 1); assert(year >= MIN_YEAR && year <= MAX_YEAR); assert(day <= days_in_month[month - 1] || (day == 29 && month == 2 && isleap(year))); if (!init_flag) init_date(); d = (year > MIN_YEAR ? cdays[year - MIN_YEAR - 1] : 0); for (i = 1; i < month; i++) d += days_in_month[i - 1] + (i == 2 && isleap(year)); d += day; return d; } // convert internal date to m/d/y void Date::d_to_mdy(Drep d, int& month, int& day, int& year) { int i; Drep t; if (!init_flag) init_date(); for (i = MIN_YEAR; i <= MAX_YEAR; i++) { if (d <= cdays[i - MIN_YEAR]) break; } assert(i <= MAX_YEAR); if (i > MIN_YEAR) d -= cdays[i - MIN_YEAR - 1]; year = i; for (i = 1; i <= 12; i++) { if (d <= (t = days_in_month[i - 1] + (i == 2 && isleap(year)))) break; d -= t; } assert(i <= 12); month = i; day = d; } // constructor from a Drep Date::Date(Drep dt) { assert(dt <= MAX_DAY); d = dt; } #define ISDEL(c) ((c) == ',' || (c) == '-' || (c) == '/') static const char* mon[12] = { "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec" }; // constructor from a char* string Date::Date(const char* s) { char buf[3][25]; int i; int j; int mo; int dy; int yr; assert(s && *s); // break into fields i = 0; for (;;) { if (i == 3) break; while (*s && (*s <= ' ' || ISDEL(*s))) s++; if (!*s) break; j = 0; if (isdigit(*s)) { while (isdigit(*s)) buf[i][j++] = *s++; buf[i][j] = 0; i++; } else if (isalpha(*s)) { while (isalpha(*s)) buf[i][j++] = tolower(*s++); buf[i][j] = 0; i++; } else { break; } } assert(i == 3); // month i = 0; if (isalpha(buf[1][0])) i = 1; if (isalpha(buf[i][0])) { if (buf[i][3]) buf[i][3] = 0; for (j = 0; j < 12; j++) { if (!strcmp(buf[i], mon[j])) break; } j++; mo = j; } else { mo = atoi(buf[i]); } // day i = !i; dy = atoi(buf[i]); // year yr = atoi(buf[2]); if (yr < 100) yr += 1900; d = mdy_to_d(mo, dy, yr); } // copy constructor Date::Date(const Date& x) { d = x.d; } // constructor from m/d/y Date::Date(int month, int day, int year) { d = mdy_to_d(month, day, year); } // conversion operator to Drep Date::operator Drep() { return d; } // print a date void Date::print(char* s) { int month; int day; int year; char buf[25]; char* t; d_to_mdy(d, month, day, year); t = (s ? s : buf); sprintf(t, "%02d/%02d/%4d", month, day, year); if (!s) printf("%s", t); } // get m/d/y from internal void Date::get_mdy(int& mo, int& dy, int& yr) { d_to_mdy(d, mo, dy, yr); } // difference in days of two dates long Date::operator-(const Date& dt) { return long(d) - long(dt.d); } // day of week int Date::dow() { Drep dw = (d - 1) % 7 + DOW_MIN; if (dw > 7) dw -= 7; return dw; } // working days between two dates long Date::wdays(const Date& dt) { long n; Drep i; int mo, dy, yr; int dw; assert(d <= dt.d); n = 0; for (i = d; i <= dt.d; i++) { Date x(i); dw = x.dow(); if (dw == 1) // sunday continue; if (dw == 7) // saturday continue; x.get_mdy(mo, dy, yr); if (mo == 5 && dy >= 25 && dw == 2) // memorial continue; if (mo == 9 && dy <= 7 && dw == 2) // labor continue; if (mo == 11 && dy >= 22 && dy <= 28 && dw == 5) // thanks continue; if (mo == 1 && dy == 1) // new years continue; if (mo == 12 && dy == 31 && dw == 6) continue; if (mo == 1 && dy == 2 && dw == 2) continue; if (mo == 7 && dy == 4) // 4th july continue; if (mo == 7 && dy == 3 && dw == 6) continue; if (mo == 7 && dy == 5 && dw == 2) continue; if (mo == 12 && dy == 25) // christmas continue; if (mo == 12 && dy == 24 && dw == 6) continue; if (mo == 12 && dy == 26 && dw == 2) continue; n++; } return n; } #ifdef DRIVER int main() { char buf[25]; for (;;) { printf("date 1: "); gets(buf); Date d1(buf); printf("date 2: "); gets(buf); Date d2(buf); printf("calendar days = %ld\n", d2 - d1); printf("work days = %ld\n\n", d1.wdays(d2)); } return 0; } #endif 1. This class represents calendar dates for the years 1875 to 2025. An actual date is stored as an absolute day number with January 1, 1875 as the basis. There are other ways of storing dates, for example by representing the month/day/year as integers. 2. The header file uses an include guard __DATE_H__ so that it can be included multiple times without error. It's common in large programming projects to have headers included more than once. 3. The Date class uses a set of private static utility functions, for example one that determines if a given year is a leap year or not. These functions are private to the class but do not operate on object instances of the class. 4. There are a set of constructors used to build Date objects. One of these is a copy constructor and two others are used to create Date objects from a month/day/year set of numbers, or from a string which has the date formatted in one of several forms: September 25, 1956 9/25/56 9 25 56 This particular constructor will be confused by dates written in the European format, for example: 25/9/56 5. There are member functions for determining what day of the week a given date is (Sunday - Saturday), and for computing the number of days between two dates. 6. There is also a member function for computing the number of work days between two dates (inclusive of beginning and end dates). This function is somewhat arbitrary and encodes rules used in the United States, including boundary holidays (for example, if New Year's is on a Sunday, Monday will be taken as a holiday). 7. The functions for turning month/day/year into an internal number, and vice versa, use a precomputed vector that gives the cumulative days since 1875 for a given year. Given this vector, the approach is straightforward and brute force. 8. The day of week calculation uses modulo arithmetic, based on a known day of week for January 1, 1875. 9. There are various other ways of handling dates. For example, the UNIX system represents time as the number of seconds since midnight UTC on January 1, 1970. For file timestamps and so on, a date system with a granularity of a whole day would not work. As another example, the western world changed its calendar system in September of 1752, and the above code would not work across this boundary, even if the Drep representation would handle the number of days involved. ACKNOWLEDGEMENTS Thanks to Nathan Myers, Eric Nagler, David Nelson, Terry Rudd, Jonathan Schilling, and Clay Wilson for help with proofreading. SUBSCRIPTION INFORMATION / BACK ISSUES To subscribe to the newsletter, send mail to majordomo@world.std.com with this line as its message body: subscribe c_plus_plus Back issues are available via FTP from: rmii.com /pub2/glenm/newslett or on the Web at: http://www.rmii.com/~glenm There is also a Java newsletter. To subscribe to it, say: subscribe java_letter using the same majordomo@world.std.com address. ------------------------- Copyright (c) 1996 Glen McCluskey. All Rights Reserved. This newsletter may be further distributed provided that it is copied in its entirety, including the newsletter number at the top and the copyright and contact information at the bottom. Glen McCluskey & Associates Professional C++ Consulting Internet: glenm@glenmccl.com Phone: (800) 722-1613 or (970) 490-2462 Fax: (970) 490-2463 FTP: rmii.com /pub2/glenm/newslett (for back issues) Web: http://www.rmii.com/~glenm