Reputation: 163
I'm trying to write a simple generalised function for iteration over container elements. Every element is converted to std::string
(no matter how) and stored in another place. The basic version was trivial:
template<class Container>
void ContainerWork(const Container& c)
{
for(const auto& elem : c) {
/* convert to string and store*/
}
}
Then it became necessary to add specialisation for containers with value type std::string
and code transformed to:
template<typename T, template<typename, typename> class Container, class Allocator>
void ContainerWork(Container<T, Allocator> c)
{
for(const T& elem : c) {
/* convert to string and store*/
}
}
template<template<typename, typename> class Container, class Allocator>
void ContainerWork(Container<std::string, Allocator> c)
{
for(const std::string& elem : c) {
/* frame elem in quotes*/
}
}
It works great, but now I can use only sequenced containers (vector
, list
, etc.), but I want also use set
and and unordered_set
. Any ideas how do this without "copy-paste" realisation for container with 4 params? I am trying to play with decltype(Container)::value_type
but with no luck.
I may use most of c++11 features (compiler - VS2012 or GCC 4.8.x)
Upvotes: 6
Views: 362
Reputation: 76778
As far as I understand this, you could just use std::for_each
with a polymorphic functor:
namespace detail {
template<typename T>
std::string do_conversion(const T& item) {
// convert to string
}
template<>
std::string do_conversion<std::string>(const std::string& item) {
// frame in quotes
}
}
struct convert_to_string {
template<typename T>
void operator()(const T& item) {
// delegate to free function in namespace scope, because otherwise we
// cannot specialize
std::string result = detail::do_conversion(item);
}
};
// in calling code
std::for_each(std::begin(container), std::end(container), convert_to_string{});
Does this work for you?
Upvotes: 1
Reputation: 275310
// implement the bodies as functors to allow for partial
// specialization for value_type:
template <class value_type>
struct DoContainerWorkImpl {
template<typename Container>
void operator()(const Container& c) const
{
for (auto&& x : c) {
// code
}
}
};
template <>
struct DoContainerWorkImpl<std::string> {
template<typename Container>
void operator()(const Container& c) const
{
for (std::string const& x : c) {
// code
}
}
};
template <class Container>
void DoContainerWork(const Container& c)
{
using std::begin; // enable ADL of begin
// extract the iterator for the container c:
typedef typename std::decay<decltype( std::begin(c) )>::type iterator;
// extract the value type of the container:
typedef typename std::iterator_traits<iterator>::value_type value_type;
DoContainerWorkImpl<value_type>()( c );
}
another approach would involve making the algorithm one thing, and determining if we want to call the conversion within the body of the algorithm using a traits class or other technique "locally".
Here is a stab at that approach:
// convert non-strings:
template<typename T>
std::string as_string( T&& t )
{
// converting code goes here
}
// block dangling rvalues:
std::string as_string( std::string&& s ) { return std::move(s); }
// pass lvalues through:
std::string const& as_string( std::string const& s ) { return s; }
std::string const& as_string( std::string& s ) { return s; }
now, we do this in your loop body:
for(auto&& elem : c) {
std::string const& s = as_string(std::forward<decltype(elem)>(elem));
// code using s as a string
}
which compiles to a noop (or, at worst a few moves) if the container is a container of std::string
, and to converting code if it is not.
Lifetime extension of temporaries assigned to references keeps any temporary generated std::string
around, and lvalue lifetime of the iterator keeps the "string container whose iterators return references" lifetime long enough.
Upvotes: 2
Reputation: 171117
That's why all of the standard library's algorithms work on iterators rather than containers.
You can change your core function to work on iterators rather than containers. This will require partial specialisation, which doesn't exist for function templates, so we'll use the delegate-to-class trick:
template <typename It>
void DoIteratorWork(It start, It end)
{
DoIteratorWork_Impl<It, typename std::iterator_traits<It>::value_type>::call(start, end);
}
template <typename It, typename ValueType>
struct DoIteratorWork_Impl
{
static void call(It start, It end)
{
for (; start != end; ++start) {
// operate on *it
}
}
};
template <typename It>
struct DoIteratorWork_Impl<It, std::string>
{
static void call(It start, It end)
{
for (; start != end; ++start) {
// operate on *it
}
}
};
If you really want to, you can then create a wrapper around this:
template <class Container>
void DoContainerWork(const Container& c)
{
using std::begin; using std::end; // enable ADL of begin and end
return DoIteratorWork(begin(c), end(c));
}
Upvotes: 7
Reputation: 55887
You can use variadic-templates
, something like
template<typename T, template<typename...> class Container, typename... Args>
void ContainerWork(Container<T, Args...> c)
{
}
template<template<typename...> class Container, typename... Args>
void ContainerWork(Container<std::string, Args...> c)
{
}
And probably you can of course use simple dispatch
template<typename Container>
void ContainerWork(Container c,
typename std::enable_if<!std::is_same<typename Container::value_type,
std::string>::value>::type* = 0)
{
}
template<typename Container>
void ContainerWork(Container c,
typename std::enable_if<std::is_same<typename Container::value_type,
std::string>::value>::type* = 0)
{
}
But anyway, if difference is only in call to convert function - you can simply overload it for T
and for string
, version for string
will simple return this string.
Also you can use SFINAE
with C++11 decltype
feature
template<typename Container>
auto ContainerWork(Container c) ->
decltype(c.begin(),
typename std::enable_if<!std::is_same<typename Container::value_type,
std::string>::value, void>::type())
{
}
template<typename Container>
auto ContainerWork(Container c) ->
decltype(c.begin(),
typename std::enable_if<std::is_same<typename Container::value_type,
std::string>::value, void>::type())
{
}
Upvotes: 5