C++ - A Better Custom ForEach

(1/1)

Richard Marks:
My previous attempt resulted in non-portable, shitty C void pointer hell code. ::)
My second take on the issue of implementing a custom ForEach system revolves around C++ template functions, classes, and functors.

Its relatively robust, and highly usable.
I make use of the STL's std::vector container, however if you so chose, you could easily modify the code to use anything you wanted as a container.

The big part about this system is the simplicity of its use. Look at the main() function at the end to see what I mean.

Code:

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <vector>

// all-new custom ForEach system. :)
// (C) 2010, Richard Marks
// usage is OK as long as you give a line of credit.

// ForEach Template - 3 template parameters are used.
// Type - data type of the container
// Klass - name of the processing class
// RetVal - data type for the return value
// The function takes a reference to the container to process
template <typename Type, typename Klass, typename RetVal>
RetVal ForEach(std::vector<Type>& dataSource)
{
// create a processor object from our specified class
Klass processor;

// create an iterator for the container
typename std::vector<Type>::iterator iter;

// iterate over the container
size_t dataSize = dataSource.size(), index = 0;
for (iter = dataSource.begin(); iter != dataSource.end(); iter++)
{
// process the element
processor(index++, dataSize, *iter);
}

// return the processor's results
return processor.GetResult();
}

// ForEach Template - 2 template parameters are used.
// Type - data type of the container
// Klass - name of the processing class
// The function takes a reference to the container to process
template <typename Type, typename Klass>
void ForEach(std::vector<Type>& dataSource)
{
// create a processor object from our specified class
Klass processor;

// create an iterator for the container
typename std::vector<Type>::iterator iter;

// iterate over the container
size_t dataSize = dataSource.size(), index = 0;
for (iter = dataSource.begin(); iter != dataSource.end(); iter++)
{
// process the element
processor(index++, dataSize, *iter);
}
}

// processor class
// Name: SumOf
// purpose: calculate the sum of all the integers in a container
// usage: pass the name of the class to the custom ForEach template function as the Klass parameter.
// Note: all processor classes need to define a GetResult() method, and implement the () operator.
// The () operator must have 3 parameters:
// size_t index,
// size_t count,
// T value - where T is the data type of the container, so if you are processing integers, T is an int.
class SumOf
{
public:
SumOf() { _sum = 0; }
const int GetResult() const { return _sum; }
void operator()(size_t index, size_t count, int value)
{
// some info regarding the params passed - useful for debugging
// you don't need this line of course.
std::cout << "Called SumOf::operator(index=>" << index << ", count=>" << count << ", value=>" << value << ");" << std::endl;

// add the value to the processor class's sum
_sum += value;

// display some info to the console so we can get some feedback on the sum.
// again, this isn't needed, just for debugging.
std::cout << (_sum - value) << " + " << value << " = " << _sum << std::endl;
}
private:
int _sum;
};

// processor class
// Name: ValuePrinter
// purpose: print every element in a container to stdout
// usage: pass the name of the class to the custom ForEach template function as the Klass parameter.
// Note: this processor class is a template class, so you can re-use this for any container data
template <typename Type> class ValuePrinter
{
public:
const int GetResult() const { return 0; } // unused for this class, but required.
void operator()(size_t index, size_t count, Type value)
{
// some info regarding the params passed - useful for debugging
// you don't need this line of course.
std::cout << "Called ValuePrinter::operator(index=>" << index << ", count=>" << count << ", value=>" << value << ");" << std::endl;

// output the value to stdout
std::cout << value << std::endl;
}
};

int main(int argc, char* argv[])
{
// create a few vectors to hold the data
std::vector<int> numbers;
std::vector<char> letters;
std::vector<const char*> strings;

// fil the vectors with data
numbers.push_back(1); numbers.push_back(2);
numbers.push_back(3); numbers.push_back(4);
numbers.push_back(5); numbers.push_back(6);
numbers.push_back(7); numbers.push_back(8);
numbers.push_back(9); numbers.push_back(10);

letters.push_back('H'); letters.push_back('e');
letters.push_back('l'); letters.push_back('l');
letters.push_back('o'); letters.push_back(' ');
letters.push_back('W'); letters.push_back('o');
letters.push_back('r'); letters.push_back('l');
letters.push_back('d');

strings.push_back("This"); strings.push_back("is");
strings.push_back("a"); strings.push_back("test");
strings.push_back("of"); strings.push_back("strings");

// print all the values in each vector
ForEach<int, ValuePrinter<int> >(numbers);
ForEach<char, ValuePrinter<char> >(letters);
ForEach<const char*, ValuePrinter<const char*> >(strings);

// calculate the sum of the integers in the numbers vector
int sum = ForEach<int, SumOf, int>(numbers);
printf("Final Sum = %d\n", sum);

// for kicks, print the ascii code values from each char in the letters vector :D
ForEach<char, ValuePrinter<int> >(letters);

// keep the program from terminating prematurely
printf("Press Enter to Exit Program.\n");while('\n' != fgetc(stdin));
return 0;
}



So, I learned quite a bit today. How about you? ;D
Time for bed. Let me know what you think.

RedSlash:
A tad better than before, but again does not compile with gcc. One minor and one major issue.

Incorrect:
Code:

std::vector<Type>::iterator iter;


Since you are defining a type and std::vector<Type> is a dependant name, it needs to prefixed with typename so satisfy C++ name resolution rules as defined by the C++ standard.

Correct:
Code:

typename std::vector<Type>::iterator iter;


Will be accepted by both microsoft and gcc.

Dangerous:
Code:

template <typename Type>
std::vector<Type>& FillVector(std::vector<Type>& v, int count, ...)
{
va_list args;
va_start(args, count);
for (int i = 0; i < count; i++)
{
Type value = va_arg(args, Type);
v.push_back(value);
}
va_end(args);
return v;
}

...

// You will get a warning that this line will crash with GCC
FillVector<char>(letters, 11, 'H','e','l','l','o',' ','W','o','r','l','d');


It might not be obvious at first why this is dangerous, but the general rule with templates is to never mix template with varargs. The chars in the FillVector<char> line are implicitly promoted to int, but because the template specification is operating on the char type, this is like casting int * onto char *.

Output of your program (Ubuntu x64):
Code:

Illegal instruction


Richard Marks:
Quote from: RedSlash on March 29, 2010, 09:39:09 PM

A tad better than before, but again does not compile with gcc. One minor and one major issue.

Incorrect:
Code:

std::vector<Type>::iterator iter;


Since you are defining a type and std::vector<Type> is a dependant name, it needs to prefixed with typename so satisfy C++ name resolution rules as defined by the C++ standard.

Correct:
Code:

typename std::vector<Type>::iterator iter;


Will be accepted by both microsoft and gcc.


Whoops! I thought I already had that. ::) I guess I missed it in a few places.

Quote

Dangerous: ...stuff snipped...


I just added the utility function to make filling the vectors quick for the demo.
That function was just for my own quick use, since MOST programs will fill the vectors from a data source, not hard-coded.

RedSlash:
Code:

That function was just for my own quick use, since MOST programs will fill the vectors from a data source, not hard-coded.
I was assuming these are some kind of tutorial series of some sort. It's probably a good idea to set off a good example as anyone who comes by will want to copy and compile your code.

Richard Marks:
Yeah, you're right. I replaced the code dump in the opening post.

Navigation

[0] Message Index