2
votes

I have a class template. Within this class template, I am trying to define a member function template which accepts const_iterators on a collection of strings. The collection itself can be any kind of StdLib collection, but realistically it will be either a vector or a list.

Since the collection can be any type, I am using a template-template parameter to specify the collection type. However it will always be a collection of string. I want template argument deduction to work so that I don't have to specify the collection type when calling the member function.

The code that follows in an SSCCE that resembles my intended use-case.

So far, I have for the class definition (Live Demo):

template <typename Foo>
struct Gizmo
{
    Foo mF;
    Gizmo (Foo f) : mF (f) {};

    template <template <typename> class Cont> void DoIt(
        typename Cont <string>::const_iterator begin,
        typename Cont <string>::const_iterator end)
        {
            stringstream ss;
            ss << "(" << this->mF << ")\n";
            const std::string s = ss.str();
            copy (begin, end, ostream_iterator <std::string> (cout, s.c_str()));
        }
};

Compilation of instantiation of the class template succeeds:

int main()
{
    list <string> l;
    l.push_back ("Hello");
    l.push_back ("world");

    Gizmo <unsigned> g (42);
}

However when I try to leverage argument deduction (without which, this whole exercise is almost pointless):

g.DoIt (l.begin(), l.end());

GCC complains that it can't deduce the template argument:

prog.cpp: In function ‘int main()’:
prog.cpp:34:28: error: no matching function for call to ‘Gizmo<unsigned int>::DoIt(std::list<std::basic_string<char> >::iterator, std::list<std::basic_string<char> >::iterator)’
  g.DoIt (l.begin(), l.end());
                            ^
prog.cpp:34:28: note: candidate is:
prog.cpp:16:49: note: template<template<class> class typedef Cont Cont> void Gizmo<Foo>::DoIt(typename Cont<std::basic_string<char> >::const_iterator, typename Cont<std::basic_string<char> >::const_iterator) [with Cont = Cont; Foo = unsigned int]
  template <template <typename> class Cont> void DoIt(
                                                 ^
prog.cpp:16:49: note:   template argument deduction/substitution failed:
prog.cpp:34:28: note:   couldn't deduce template parameter ‘template<class> class typedef Cont Cont’
  g.DoIt (l.begin(), l.end());

Ultimately all I really care about is being able to call DoIt with begin and end iterators on a collection of string. The actual type of collection can either be vector or list, and I don't want to have to either specify the template arguments, nor do I want to overload based on container.

How can I get this to work?

Note that my actual use-case will be in C++03. C++11 solutions are welcomed, but I'll only be able to accept a C++03 solution.

5
Isn't this an example of a non-deduced context? - user2033018
@faranwath: Ah. Hm. Maybe it is. I'm not married to using template-template parameters. Can you think of an alternative? - John Dibling
Is there a reason for parametrising by container, rather than by iterator? - Mike Seymour
@zch: That won't work because vector is not a type. Only vector <string> is. Make sense? - John Dibling
@MikeSeymour: Yes, but arguably it might not be a good reason. I think I might have a hard time defending it. But beyond that, I became stubborn when the compiler wouldn't bend to my will. - John Dibling

5 Answers

2
votes

There were a couple of issues. I fixed the template template parameter for you. I also modified the method signature so you can auto deduce types, but it requires passing in of the original collection:

#include <iostream>
#include <vector>
#include <list>
#include <string>
#include <sstream>
#include <iterator>
using namespace std;


template <typename Foo>
struct Gizmo
{
    Foo mF;
    Gizmo (Foo f) : mF (f) {};

    template <template <typename T, typename A = allocator<T> > class Cont> void DoIt(
        const Cont <string> &, // deduction
        const typename Cont <string>::iterator &begin,
        const typename Cont <string>::iterator &end)
        {
            stringstream ss;
            ss << "(" << this->mF << ")\n";
            const std::string s = ss.str();
            copy (begin, end, ostream_iterator <std::string> (cout, s.c_str()));
        }
};

int main()
{
    list <string> l;
    l.push_back ("Hello");
    l.push_back ("world");

    Gizmo <unsigned> g (42);
    g.DoIt (l, l.begin(), l.end());
}

See it run here.

1
votes

It seems like I'm missing the point, but why can't you do this?

template <typename Iterator> void DoIt(
    Iterator begin,
    Iterator end)
    {
      // snip
    }

// [...]

list <string> l;
l.push_back ("Hello");
l.push_back ("world");
vector <string> v;
v.push_back ("Hello");
v.push_back ("world");

Gizmo <unsigned> g (42);
g.DoIt (l.begin(), l.end());
g.DoIt (v.begin(), v.end());
1
votes

I submit that you don't actually care if your input is a vector, or a list, or even a container. I think all you are actually concerned with is that you have a sequence of things that you can iterate over that are convertible to string. So you should accept any pair of iterators whose value_type is_convertible to string (Live demo at Coliru):

template <typename Iter>
typename enable_if<
  is_convertible<
    typename iterator_traits<Iter>::value_type,string
  >::value
>::type DoIt(Iter begin, Iter end) const
{
    stringstream ss;
    ss << "(" << this->mF << ")\n";
    const std::string s = ss.str();
    copy (begin, end, ostream_iterator <std::string> (cout, s.c_str()));
}

I apologize for the ugliness of the constraint, Concepts Lite can't get here soon enough for me.

0
votes

The problem is that for non-const strings begin and end functions return string::iterator, but not string::const_iterator. You should write a similar function, only with string::iterator. Or, in c++11 you can use cbegin and cend functions.

0
votes

This is a non-deduced context and you cannot work around that.

Given an iterator, you just cannot possibly tell what kind of container it belongs to. A raw pointer can concievably serve as an iterator for more than one type of container, for example.

Fortunately you don't ever use the container type for anything so you may just throw it away and parameterize on the iterator.

But I do use it to make sure my container contains strings!

No you do not. Who told you Foo<string>::iterator dereferences to a string (or that it exists, for that matter)? Of course if it exists, it probably dereferenced to a string, becsuse of existing conventions, but this is by no means guaranteed.