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.
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?
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.
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 ?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With