views:

92

answers:

4

I have a function where I have a container which holds strings (eg vector<string>, set<string>, list<string>) and, given a start iterator and an end iterator, go through the iterator range processing the strings.

Currently the function is declared like this:

template< typename ContainerIter>
void ProcessStrings(ContainerIter begin, ContainerIter end);

Now this will accept any type which conforms to the implicit interface of implementing operator*, prefix operator++ and whatever other calls are in the function body.

What I really want to do is have a definition like the one below which explicitly restricts the amount of input (pseudocode warning):

template< typename Container<string>::iterator>
void ProcessStrings(Container<string>::iterator begin, Container<string>::iterator end);

so that I can use it as such:

vector<string> str_vec;
list<string> str_list;
set<SomeOtherClass> so_set;

ProcessStrings(str_vec.begin(), str_vec.end());  // OK
ProcessStrings(str_list.begin(), str_list.end());  //OK
ProcessStrings(so_set.begin(), so_set.end());  // Error

Essentially, what I am trying to do is restrict the function specification to make it obvious to a user of the function what it accepts and if the code fails to compile they get a message that they are using the wrong parameter types rather than something in the function body that XXX function could not be found for XXX class.

+3  A: 

You can get close to this with a template template parameter:

template<template<class> class CONTAINER>
void ProcessStrings(CONTAINER<string>&);

This will process a whole container, and give a compile error if it doesn't contain strings.

ProcessStrings(str_vec); // OK
ProcessStrings(so_set); // Error

If you want to work with iterator ranges, then the best I could manager is

template<template<class> class CONTAINER>
void ProcessStrings(typename CONTAINER<string>::iterator, 
                    typename CONTAINER<string>::iterator);

Unfortunately, type inference won't work on the function arguments, so you'll have to explicitly give the template parameter:

ProcessStrings<vector>(str_vec.begin(), str_vec.end()); // OK
ProcessStrings<set>(so_set.begin(), so_set.end()); // Error

Can anyone improve on this?

Mike Seymour
I would be surprised it worked, using `template <class> CONTAINER` is messy because you need to specify the exact number of template parameter and not only there are several arities depending on which container we're talking about but there's also the issue that a STL implementation may add some supplementary parameters as long as they have proper defaults....
Matthieu M.
@Matthieu: I'm pretty sure template argument deduction can fill in default arguments to find a match. Certainly, this code worked when I tested it with gcc.
Mike Seymour
+2  A: 

You can implement such checks using boost::enable_if template magic. The method below will not be compiled unless the iterator's value type is of type string.

template<class It>
boost::enable_if_c< 
        boost::is_same< typename boost::iterator_value<It>::type, string >::value
>::type
ProcessStrings(It itBegin, It itEnd) 
{ }

If boost::iterator_value<It>::type is of type string, boost::enable_if<...>::type will evaluate to void, your return parameter. Otherwise, due to the SFINAE (substitution-failure is not an error) principle, the method will not be compiled without an error.

Sebastian
Either `enable_if` or a `static_assert` within the function body should work. Remove the `void` return type (it's `enable_if`'s job to specify it here).
Matthieu M.
@Matthieu Thanks, you're right. The void return value was a typo.
Sebastian
A: 

Try

#include <string>
#include <vector>
#include <list>

template<template<typename T,typename A> class C>
void ProcessStrings(typename C<std::string, std::allocator<std::string> >::iterator begin,
                    typename C<std::string, std::allocator<std::string> >::iterator end)
{
}


int main()
{
    std::vector<std::string>    strVec;
    std::list<std::string>      strList;
    std::list<int>              intList;

    ProcessStrings<std::vector>(strVec.begin(), strVec.end());
    ProcessStrings<std::list>(strList.begin(), strList.end());
    ProcessStrings<std::list>(intList.begin(), intList.end());  // This will fail
}
Martin York
Could you please expand on how this answer differs from Mike's? I see it specifies the allocator parameter, but is that really a crucial difference?
Rob Kennedy
I did not see Mikes when I posted. But I just tried it and I can't seem to get it to work without explicitly specifying all the type parameters in the container template. I do not know if this is my compiler or something else I have not tried to solve the problem too hard.
Martin York
+1  A: 

A simple but effective way.

template <class T>
struct is_basic_string: boost::mpl::false_ {};

template <class CharT, class Traits, class Alloc>
struct is_basic_string< std::basic_string<CharT, Traits, Alloc> >:
   boost::mpl::true_ {};

And then use it to check the value type

void ProcessStrings(Iterator begin, Iterator end)
{
  BOOST_MPL_ASSERT_MSG(
    is_basic_string< typename boost::value_type<Iterator>::type >,
    ONLY_ACCEPT_ITERATOR_TO_BASIC_STRING,
    (Iterator)
  );

  // do your work
}

Check the reference here, this macro is meant to provide as meaningful messages as possible at compilation time.

Also this is slightly more generic than Sebastian solution... but std::wstring are quite handy for internationalization and you would not want to choke on that.

Now the very good question is... why would you want to do that!

The very goal of generic programming is to produce functions which can work with any type that complies to the operations they use internally. Why do you want to intentionally restrict this ?

Matthieu M.
Thanks for the extra details about using `basic_string`, an interesting extention to the question. I have responded to a comment by @jmucchiello on the original question that I hope provides some sort of explanation for your question in the last paragraph.
Rodion Ingles