Mr. Cluey : How To ...? : Overloading Methods


Can I overload functions in 4 Test?

No, you can't. The 4 Test language does not support function overloading.

But you can fake it, without too much difficulty, using some of the special modifiers that 4Test permits within function declarations. It won't be as clean as genuine overloading, but it gets the job done.

What is overloading?

Overloading allows a set of functions that perform a similar operation, such as print(), to be collected under a common mnemonic name. The resolution of which function instance is meant is transparent to the user, removing the lexical complexity of providing each function with a unique name....

Stanley B. Lippman

C++ Primer

In other words, function overloading is used when you have a single task, which has two or more implementations or algorithms depending on the particular data in use. By overloading the function, you hide from the user the fact that there is more than one algorithm to choose from.

Alternatives to function overloading

Here are some of the simpler alternatives to function overloading which may serve in its place.

Default Parameters

The answers to two questions will tell you whether using default parameters is a legitimate solution to your particular problem.

First, is there a value that you can use for a default? Second, how many algorithms do you want to use? In general, if you can choose a reasonable default value, and you want to employ only a single algorithm, you'll use default parameters.

Steve Meyers

Effective C++

For instance, suppose you that you use the FileOpen function often; usually, but not always, specifying FM_READ as your file mode. You might reasonable write a wrapper function that uses FM_READ by default.

MyFileOpen ( string sFileName, FILEMODE FM optional )
{
	if IsNull( FM ) FM = FM_READ ;
	FileOpen( sFileName, FM ) ;
}

main ()
{
	string exec = "c:\autoexec.bat" ;
	MyFileOpen ( exec, FM_READ ) ; //open for read...
	MyFileOpen ( exec ) ; // also open for read...
	MyFileOpen ( exec, FM_WRITE ) ; //open for write...
}

Note Meyers' two key points; FM_READ is a reasonable default value, and one single algorithm [FileOpen()] is always used.

The 4Test language itself is just littered with this kind of thing. Consider the Click Method. All three parameters have reasonable default values. In fact, were the method actually written in 4Test, you would see a syntax much like the one above...

Click ( integer iButton optional, integer iXpos optional, integer iYpos optional)
{
	if IsNull( iButton ) iButton = 1 ;
	if IsNull( iXpos ) iXpos = 0 ;
	if IsNull( iYpos ) iYpos = 0 ;

	//code to execute the click follows...
}

TypeOf()

Typically, overloaded functions depend upon the type of their data to determine which algoritm to use. This determination is made when the code is compiled.

The TypeOf() function allows you to determine, at run time, what type of data has been passed. Not at all the same thing, but generally good enough to get the job done.

Foo ( anytype A )
{
	switch (TypeOf(A))
	{
		case INTEGER: 
		{
			// Integer specific code goes here
			print ("integer") ;
		}
		case STRING: 
		{
			// String specific code goes here
			print ("string") ;
		}
		default: LogError("Error") ;
	}
}

main ()
{
	Foo(7) ; 		//prints "integer"
	Foo("Bob") ;		//prints "string"
	Foo( MainWin("#1") ) ;	//logs an error "Error"
}

There are a few additional complications with this approach. Overloading in C++ is robust - if your call doesn't match one of the function signatures (i.e. having the right data types in the right places) you get an error when you compile. Foo, as written above, generates the error at run-time. You don't find out, until the Foo gets called, that there is a bug in your script.

This can be improved, somewhat, by using a user defined type, rather than an anytype, in the function declaration. User defined types are created using the type ... is statement.

type FOOVAR is string, integer ;

Foo ( FOOVAR A )
{
	switch (TypeOf(A))
	{
		case INTEGER: 
		{
			// Integer specific code goes here
			print ("integer") ;
		}
		case STRING: 
		{
			// String specific code goes here
			print ("string") ;
		}
		default: 
		{
			//This never happens - a FOOVAR is always an INTEGER
			//or a string.
			LogError("Error") ;
		}
	}
}

main ()
{
	Foo(7) ; 		//prints "integer"
	Foo("Bob") ;		//prints "string"
	Foo( MainWin("#1") ) ;	//DOES NOT COMPILE
}

When dealing with fundamental data types, this is all straight forward. However, you may need to distiguish between two data types of the same fundamental type. TypeOf, by itself, will let you down. For instance, if you need to distinguish between a WNDTAG and a STRING, TypeOf can't help you - WNDTAG is a user defined type derrived from STRING. 4Test.inc defines all of the "standard" types, so you can learn in advance what to expect.

If TypeOf doesn't do the job, all may not be lost. The select statement allows you to use more complicated boolean expressions to select your branch, so a fragment like this might work

	select 
	{
		case IsWNDTAG(A): 
		{
			// WNDTAG specific code goes here
			print ("wndtag") ;
		}
		case ( TypeOf(A) == string ): 
		{
			// String specific code goes here
			print ("string") ;
		}
	}

Of course, you have to discover a good way to implement IsWNDTAG().

Varargs

Variable argument lists are used when you just aren't sure how many parameters you will need to pass to a function. Your arguments will get dumped into a LIST, and you can extract them back out again in the body of your function.

The Printf() syntax is typical of functions that use varargs. You will typically first declare all of the required parameters, then dump everything else into a varargs list. If Printf() were written in 4Test, the declaration would look something like this

Printf( string sFormat, varargs vaList )

The body of the code would then use sFormat to determine the number of elements in the valist that it would actually need. This is fairly typical of functions with vararg parameters - the required parameters determine the number of optional parameters that are used.

Another way varargs are typically used is in functions that want to iterate over the entire parameter list. For instance, if you wanted to compute the average of an arbitrary number of integers, you might do this...

Avg( varargs of integer iList )
{
	integer Total = 0 ;
	integer crnt ;
	
	for each crnt in iList
		Total += crnt ;

	print ( Total / ListCount(iList) ) ;
}

main ()
{
	Avg( 7 ) ; 	// prints "7"
	Avg( 1, 2, 3) ; // prints "2"
	Avg () ; 	// Run Time Error Error: Divide by zero
}

As the last example illustrates, you have to be careful that your implementation handles an empty vararg list correctly. Here we could have avoided the issue either by adding a runtime check to ensure that iList was not empty; better still would be to make the first integer required so that the last call to Avg would generate a type mismatch error.

All of the elements passed to the varargs list must be of the same type, although that type can be an anytype or a user defined type. You can combine vararg lists with the TypeOf() techniques listed above, allowing you to change the implementation that is used based on the data passed to the function.

Trying to combine varargs with optional parameters is not a good idea - mostly it just doesn't work (type mismatch errors are common). Even when the code compiles, the resulting functionality generally isn't very useful.

One final point - once you are inside the function body, your varargs parameter is a list, completely indistinguishable from a list passed as a parameter. This means that passing a variable number of arguments through one function to another is a real pain in the neck.

Foo( varargs iList )
{
	CountList( iList ) ;
}

Bar( varargs iList )
{
	switch( ListCount( iList ) )
	{
		case 1: CountList( iList[1] ) ;
		case 2: CountList( iList[1], iList[2] ) ;
		case 3: CountList( iList[1], iList[2], iList[3] ) ;
	
		default: raise 123, "Too many parameters" ;
	}
}

CountList ( varargs iList ) 
{
	print( ListCount(iList) ) ;
}
		
main ()
{

	Foo( 1, 2, 3) ; //prints "1"
	Bar( 1, 2, 3) ; //prints "3"

}

Forunately, this isn't an issue that comes up very often. And for what it is worth, you would have the same headache programming this in C.


Mr. Cluey : How To ...? : Overloading Methods

Return to ATS Automated Testing Resources Page