tags:

views:

163

answers:

2

Source of the problem -Accelerated C++, problem 8-5

I've written a small program that examines lines of string input, and tallies the number of times a word appears on a given line. The following code accomplishes this:

#include <map>
#include <iostream>
#include <string>
#include <vector>
#include <list>
#include <cctype>
#include <iterator>

using std::vector;         using std::string;
using std::cin;            using std::cout;
using std::endl;           using std::getline;
using std::istream;        using std::string;
using std::list;           using std::map;
using std::isspace;        using std::ostream_iterator;
using std::allocator;

inline void keep_window_open()
{
    cin.clear();
    cout << "Please enter EOF to exit\n";
    char ch;
    cin >> ch;
    return;
}

template <class Out>
void split(const string& s, Out os)
{
    vector<string> ret;
    typedef string::size_type string_size;
    string_size i = 0;

    // invariant: we have processed characters `['original value of `i', `i)'
    while (i != s.size()) {
        // ignore leading blanks
        // invariant: characters in range `['original `i', current `i)' are all spaces
        while (i != s.size() && isspace(s[i]))
            ++i;

        // find end of next word
        string_size j = i;
        // invariant: none of the characters in range `['original `j', current `j)' is a space
        while (j != s.size() && !isspace(s[j]))
            ++j;

        // if we found some nonwhitespace characters
        if (i != j) {
            // copy from `s' starting at `i' and taking `j' `\-' `i' chars
            *os++ = (s.substr(i, j - i));
            i = j;
        }
    }
}


// find all the lines that refer to each word in the input
map<string, vector<int> > xref(istream& in)     // works
// now try to pass the template function as an argument to function - what do i put for templated type?
//map<string, vector<int> > xref(istream& in, void find_words(vector<string, typedef Out) = split)      #LINE 1#
{
    string line;
    int line_number = 0;
    map<string, vector<int> > ret;

    // read the next line
    while (getline(in, line)) {
        ++line_number;

        // break the input line into words
        vector<string> words;   // works            // #LINE 2#
        split(line, back_inserter(words));          // #LINE 3#
        //find_words(line, back_inserter(words));   // #LINE 4# attempting to use find_words as an argument to function

        // remember that each word occurs on the current line
        for (vector<string>::const_iterator it = words.begin();
             it != words.end(); ++it)
            ret[*it].push_back(line_number);
    }
    return ret;
}

int main()
{
    cout << endl << "Enter lines of text, followed by EOF (^Z):" << endl;

    // call `xref' using `split' by default
    map<string, vector<int> > ret = xref(cin);

    // write the results
    for (map<string, vector<int> >::const_iterator it = ret.begin();
         it != ret.end(); ++it) {
        // write the word
        cout << it->first << " occurs on line(s): ";

        // followed by one or more line numbers
        vector<int>::const_iterator line_it = it->second.begin();
        cout << *line_it;   // write the first line number

        ++line_it;
        // write the rest of the line numbers, if any
        while (line_it != it->second.end()) {
            cout << ", " << *line_it;
            ++line_it;
        }
        // write a new line to separate each word from the next
        cout << endl;
    }
    keep_window_open();
    return 0;
}

As you can see, the split function is a template function to handle various types of output iterators as desired.

My problem comes when I try to generalize the xref function by passing in the templated split function as an argument. I can't seem to get the type correct.

So my question is, can you pass a template function to another function as an argument, and if so, do you have to declare all types before passing it? Or can the compiler infer the types from the way the templated function is used in the body?

To demonstrate the errors I get, comment out the existing xref function header, and uncomment the alternate header I'm trying to get working (just below the following commment line.) Also comment the lines tagged LINE 2 and LINE 3 and uncomment LINE 4, which is attempting to use the argument find_words (which defaults to split.)

Thanks for any feedback!

+1  A: 

Or can the compiler infer the types from the way the templated function is used in the body?

The answer to that question is: No.

You'll need to change 'typedef Out' to the type that back_inserter returns and you'll need to supply that same type to "= split<type_here>".

The type that back_inserter returns is specified by the standard so you should be able to find that in your C++ lib reference.

You might also try turning your xref function into a template that takes a function template as parameter (and its type). I've never tried anything like that though, not with functions anyway, so I don't know how much success it will bring. It might be what you want, might not.

If you were using c++0x you'd have a few more options that might work more like you wish.

Noah Roberts
Thanks, I'll see what I can do about digging out the type.. I did try, but it got to the point where I wasn't even sure I could pass a function template as an argument, and a google search didn't turn up much.
Darel
+1  A: 

The following is a workaround. You can use a class with a static function

struct split
{
  template <class Out>
  static apply(const string& s, Out os) { 
    // include the body of your split function or call to an existing function
  }

};

Now, make xref generic

template <typename FIND_WORDS>
map<string, vector<int> > xref(istream& in, FIND_WORDS find_words = split()) 
{

  // replace #2 and #3 by
  find_words.apply(line, back_inserter(words));

}; 
Vicente Botet Escriba
Thanks for the reply. I'm still getting an error "could not deduce template argument for 'FIND_WORDS'" when I make these changes. I'll keep plugging away at it and see if I can make it work.
Darel
add tympename where necessary or just replace find_words::apply by find_words.apply
Vicente Botet Escriba
Darel
Have you tried with FIND_WORDS find_words = split()?
Vicente Botet Escriba
Yes, just now. Same error :(
Darel