The Evolution of typename
Historical Use of class in Templates
Early C++ reused the class keyword for template definitions. However, generic programming isn't limited to class types, which created ambiguity in code.
Reasons for typename Introduction
The introduction of typename was motivated by nested types within custom classes and potential ambiguities when the same identifier appeared in different classes. Compilers couldn't always determine whether an identifier represented a type or something else.
Purpose of typename
In template definitions, typename serves to declare generic types and explicitly informs the compiler that the following identifier is a type name.
#include <iostream>
#include <string>
using namespace std;
template <typename T> // Modern approach using typename instead of class
class Demo
{
public:
Demo(T value)
{
cout << "value = " << value << endl;
}
};
template <typename T>
void displayArray(T arr[], int size)
{
for (int index = 0; index < size; index++)
{
cout << arr[index] << endl;
}
}
////////////////////////////////////////////////////////
int globalVar = 0;
class ContainerA
{
public:
// Static member
static const int MEMBER = 1;
};
class ContainerB
{
public:
// Nested type
struct MEMBER
{
int data;
};
};
template <typename T>
void demonstrateAmbiguity()
{
// This line can be interpreted in two ways, creating ambiguity:
// 1. Define pointer variable 'ptr' using nested type from T (common interpretation)
// 2. Multiply static member T::MEMBER with global variable 'globalVar'
// By default, compilers interpret this as multiplication (option 2)
// rather than the type definition we expect (option 1)
// Solution: Use typename to clarify that the following identifier is a type
typename T::MEMBER* ptr; // Explicitly indicates T::MEMBER is a type name
}
int main(int argc, char *argv[])
{
demonstrateAmbiguity<ContainerA>(); // Compiles, confirming compiler interprets as multiplication
demonstrateAmbiguity<ContainerB>(); // Fails to compile, showing it's now interpreted as type definition
return 0;
}
Alternative try-catch Syntax
try-catch in Function Declarations
The try-catch blocks can separate normal functionality code from exception handling code. They can divide a function implementation into two parts, allowing specification of potential exception types directly in function declarations and definitions.
Important Considerations
Function exception declarations represent a contract with the compiler. Once declared, functions can only throw the specified exceptions. Throwing undeclared exceptions will terminate the program. Exception declarations can also be used to define functions that don't throw exceptions.
#include <iostream>
#include <string>
using namespace std;
int calculate(int x, int y) throw(int, char) // Exception declaration
// indicates this function may throw
// int or char exceptions
{
if ((0 < y) && (y < 10))
{
return (x + y);
} else
{
throw 'x'; // Throws char type, allowed by declaration
// Could also throw int like throw 0;
// Any other type would cause program termination
}
}
// This syntax is deprecated (g++ errors, VS2013 warns)
void processData(int value) try // Normal execution code
{
cout << "calculate(value, value) = " << calculate(value, value) << endl;
}
catch (int err) // Exception handling code
{
cout << "Integer Exception: " << err << endl;
}
catch (...)
{
cout << "Unknown Exception..." << endl;
}
int main(int argc, char *argv[])
{
processData(5); // Normal execution
processData(10); // Throws exception
return 0;
}
/*
Output:
calculate(value, value) = 10
Unknown Exception...
*/