Pete explains why template specializations aren't friends, and why character constants shouldn't be treated as numbers.
To ask Pete a question about C or C++, send email to petebecker@acm.org, use subject line: Questions and Answers, or write to Pete Becker, C/C++ Users Journal, 1601 W. 23rd St., Ste. 200, Lawrence, KS 66046.
Q
I was coding a template matrix class when I came upon the following surprising error. The function containing the error was the operator== for two matrices that stored the result for each comparison in a third matrix. The function follows [1]:
template< class T > Matrix<bool> Matrix<T>::operator==( const Matrix<T>& m ) const { Matrix<bool> to_return( rows, cols, false ); for( int i = 0; i < rows; i++ ) for( int j = 0; j < cols; j++ ) // mat is a private data // member of type T** to_return.mat[i][j] = mat[i][j] == m.mat[i][j]; return to_return; }A problem arose because to_return.mat was declared private and the class I was instantiating was Matrix<int>, which did not have access to Matrix<bool>'s private data members. I was curious as to the reason behind this, and whether or not the new C++ Standard supports this. Also, is there any way around this problem? I had already defined the operator[] for a Matrix class, so I just used it, but there should be other ways. Am I right in thinking this? Tamon Gibbs
A
Yes, you're right, there is a more direct way. Let's begin, though, by understanding where the problem comes from. Access rights are applied to classes, which is why the code in a member function of class A can access private data in any object of type A, not just the object to which the member function is being applied. The reason this call doesn't work for templates is that a template itself is not a class. When you instantiate a template you create a class. So, for example, in your code Matrix<int> is a class and Matrix<bool> is a class, but the two are different classes. The code for a Matrix<int> is not permitted to access private data in a Matrix<bool>, any more than code in a class A is allowed to access private data in a class B.
To make this work, think of Matrix<int> and Matrix<bool> as two classes. If you want one class to be able to access private data in another class, add a friend declaration. Like this:
template<class T> class Matrix { template <class P> friend class Matrix; ..... };The friend declaration here declares that every instantiation of Matrix is a friend of every other instantiation of Matrix. This declaration will permit Matrix<int> to access the contents of Matrix<bool>, which is just what you need.
Note, however, that this friend declaration is something of a bludgeon, and a bit dangerous. Since every instantiation of Matrix is a friend, someone who decides to access the private members of an instance of your template can do so by creating the specialization:struct S {}; template <> class Matrix<S> { public: template <class T> void access(Matrix<T> &m) { // m's private members are accessible here } };In general I wouldn't worry about this potential abuse it is too much work for most people to go through. But it is something you need to be aware of, if for no other reason, than to have an answer for that smart aleck in your next code review who asks if this technique is dangerous.
Q
What kind of portability problems would be presented by this C++ code fragment:
int start, base, pos, val; char letter; char inbuf[50]; ... cout << "Enter a letter between A and Z: "; cin >> inbuf; letter = inbuf[0]; if ((letter >= 'a') && (letter <= 'a'+25)) letter = letter - 32; if ((letter >= 'A') && (letter <= 'A'+25)) { start = base + 4 * (letter - 'A'); val = 0; for (pos=start; pos<start+4; pos++) { val = (val << 8) + readbyte(pos); } cout << "The value stored at position " << letter << " is " << val << "\n"; } //This code obtains a letter from the //user and gets a corresponding //integer from a binary array stored //in a file. The value "base" is the //starting position for the array in //the file. The function "readbyte" //reads one byte from the file at a //given position.I'm having a problem finding references on writing portable C++, so any help would be appreciated. Gregory Wood
A
This code has some fairly serious portability problems, all stemming from the assumption that C runtime systems all use the ASCII character encoding system. C provides a set of functions to do most of what you're looking for portably the prototypes for these functions are in <ctype.h>. Let's go through the issues presented here one by one, and look at how to fix them.
In the first if statement the code is testing to see whether the input is a lowercase letter, and if so, converting it to uppercase. To do this portably, use the function toupper:
letter = toupper(letter);toupper converts lowercase letters to uppercase. It leaves anything that isn't a lowercase letter unchanged. By changing to this function we've eliminated the assumption that values in the range from 'a' to 'a'+25 all represent lowercase letters. There are character sets in which the lowercase letters are not contiguous, so we can't treat all values in that range as representing lowercase letters. We've also eliminated the assumption that we can convert lowercase letters to uppercase by subtracting 32. Different character sets might require adjustment by a different amount, and the adjustment need not be the same amount for each character.
The next if statement tests whether the input, after conversion to uppercase, represents a letter. Again, the Standard C library provides a function for performing this test:
if (isalpha(letter)) .....isalpha returns a non-zero value when its argument is either an uppercase letter or a lowercase letter. In all other cases it returns zero. By switching to this function we've eliminated the assumption that all uppercase letters are represented by values in the range from 'A' to 'A'+25. Just as we saw with lowercase letters, this assumption does not have to be true.
The next assumption is harder to eliminate: there's no function in the standard library to convert values that represent uppercase letters into a value suitable for indexing into an array. As we saw earlier, the values of uppercase letters don't have to be close to each other, so there's no simple way to transform them into indices. So let's write something a bit complicated: we'll create an array of characters initialized to hold the uppercase letters in order, look up the value we need in that array, and return the index where we found it:
int to_index(char ch) { static const char *letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; return strchr(letters, ch) - letters; }Now, this code might look a bit tricky, so let's go though it in more detail. letters is a pointer to char, pointing to a string consisting of the uppercase letters of the alphabet. We use the function strchr to find the position within letters where the character ch occurs strchr returns a pointer to that character. By subtracting the pointer letters from that pointer we get the index where that letter occurs. So, for example, if we call to_index('A') we'll get back 0. If we call to_index('C') we'll get back 2. This code works regardless of how the uppercase letters are actually encoded. Note, however, that this function was written with your application in mind: you should not call it with an argument that is not an uppercase letter. If you do, strchr will return NULL, and subtracting letters from NULL will give you a meaningless value. To make the function more robust, you can use isupper to check whether the argument is in range. If it is, do the lookup; if not, return NULL. Like this:
int to_index(char ch) { static char *letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; return isupper(ch) ? strchr(letters, ch) - letters : -1; }Now that we can map an uppercase letter to an index value, your code looks like this:
start = base + 4 * to_index(letter);In general, the way to understand portability when you're dealing with characters is to assume nothing. Treat character constants like 'a' as magic names, not as numbers. Don't assume that the value 'b' bears any relationship whatsoever to the value of 'a'. Read about the functions in <ctype.h> they'll do much of what you need to do. When they don't you'll have to write some code yourself.
Reference
[1] I've removed some consistency checks from Gibbs' code to shorten it. He had it right, though.
Pete Becker is Technical Project Leader for Dinkumware, Ltd. He spent eight years in the C++ group at Borland International, both as a developer and manager. He is a member of the ANSI/ISO C++ standardization commmittee. He can be reached by email at petebecker@acm.org.