Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accept templated parameter of stl_container_type<string>::iterator

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.

like image 671
Christopher Howlin Avatar asked May 27 '10 15:05

Christopher Howlin


3 Answers

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?

like image 181
Mike Seymour Avatar answered Nov 10 '22 07:11

Mike Seymour


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.

like image 38
Sebastian Avatar answered Nov 10 '22 09:11

Sebastian


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 ?

like image 1
Matthieu M. Avatar answered Nov 10 '22 07:11

Matthieu M.