C++ Template Class; Function with Arbitrary Container Type, How to Define It

c++ template class; function with arbitrary container type, how to define it?


Traits solution.

Generalize not more than needed, and not less.

In some cases that solution might not be enough as it will match any template with such signature (e.g. shared_ptr), in which case you could make use of type_traits, very much like duck-typing (templates are duck typed in general).

#include <type_traits>

// Helper to determine whether there's a const_iterator for T.
template<typename T>
struct has_const_iterator
{
private:
template<typename C> static char test(typename C::const_iterator*);
template<typename C> static int test(...);
public:
enum { value = sizeof(test<T>(0)) == sizeof(char) };
};

// bar() is defined for Containers that define const_iterator as well
// as value_type.
template <typename Container>
typename std::enable_if<has_const_iterator<Container>::value,
void>::type
bar(const Container &c, typename Container::value_type const & t)
{
// Note: no extra check needed for value_type, the check comes for
// free in the function signature already.
}

template <typename T>
class DoesNotHaveConstIterator {};

#include <vector>
int main () {
std::vector<float> c;
bar (c, 1.2f);

DoesNotHaveConstIterator<float> b;
bar (b, 1.2f); // correctly fails to compile
}

A good template usually does not artificially restrict the kind of types for which they are valid (why should they?). But imagine in the example above you need to have access to an objects const_iterator, then you can use SFINAE and type_traits to put those constraints on your function.


Or just to as the standard library does

Generalize not more than needed, and not less.

template <typename Iter>
void bar (Iter it, Iter end) {
for (; it!=end; ++it) { /*...*/ }
}

#include <vector>
int main () {
std::vector<float> c;
bar (c.begin(), c.end());
}

For more such examples, look into <algorithm>.

This approach's strength is its simplicity and is based on concepts like ForwardIterator. It will even work for arrays. If you want to report errors right in the signature, you can combine it with traits.


std containers with signature like std::vector (not recommended)

The simplest solution is approximated by Kerrek SB already, though it is invalid C++. The corrected variant goes like so:

#include <memory> // for std::allocator
template <template <typename, typename> class Container,
typename Value,
typename Allocator=std::allocator<Value> >
void bar(const Container<Value, Allocator> & c, const Value & t)
{
//
}

However: this will only work for containers that have exactly two template type arguments, so will fail miserably for std::map (thanks Luc Danton).


Any kind of secondary template arguments (not recommended)

The corrected version for any secondary parameter count is as follows:

#include <memory> // for std::allocator<>

template <template <typename, typename...> class Container,
typename Value,
typename... AddParams >
void bar(const Container<Value, AddParams...> & c, const Value & t)
{
//
}

template <typename T>
class OneParameterVector {};

#include <vector>
int main () {
OneParameterVector<float> b;
bar (b, 1.2f);
std::vector<float> c;
bar (c, 1.2f);
}

However: this will still fail for non-template containers (thanks Luc Danton).

C++ functions to fill in and print arbitrary container

First, printing (the way you do it) can be in a slightly more compact form:

#include <iostream>
#include <iterator>

template<class C> std::ostream &printContainer(std::ostream &s, C const &c) {
std::copy(c.begin(), c.end(), std::ostream_iterator<typename C::value_type>(s, " "));
return s;
}

Though it's not important. Now, for adding elements to arbitrary container you can use the marvels of SFINAE:

template<class C, class T>
decltype(std::declval<C>().insert(std::declval<T>()), C{}) fillContainer(C c) {
std::for_each(in<T>(std::cin), in<T>(), [&c](T const &t){ c.insert(t); });
return c;
}

Or maybe even

template<class C>
decltype(std::declval<C>().insert(std::declval<typename C::value_type>()), std::declval<C &>()) fillContainer(C &c) {
using T = typename C::value_type;
std::for_each(in<T>(std::cin), in<T>(), [&c](T const &t){ c.insert(t); });
return c;
}

What lives inside the parentheses after decltype is a comma operator in an unevaluated context. Were such an expression found on its own on a separate line, the program would never be built (std::declval does not have definition, for instance), but merely as an expression to be checked for type-validity it's okay. So, this overload is only chosen when c.insert(t) compiles (and C{} after the comma gives us a return type for function.) For vectors (or strings), you can add another:

template<class C, class T>
auto fillContainer(C c) -> decltype(c.push_back(std::declval<T>()), C{});

(Here, I've put the SFINAEing decltype into a trailing return type, which in this particular case saves us one call to std::declval, as by this moment we already have c of type C, so we don't need to call std::declval to create a reference to C.)

writing templated member function over container type outside a template class

Does this solve your problem?

template <class traits>
void Foo<traits>::Initialize( typename traits::container_t& t ) {
// code
}

Can a function template deduce a container's value type?

I ended up going with something based on the answer provided by Frank and the comment posted by StoryTeller - Unslander Monica. The former provided a solution that handled more than just QSet, while the latter provided a solution that was simpler overall.

template<typename ContainerT, typename Pred>
int removeItems(ContainerT& container, Pred shouldRemove)
{
int numRemoved = 0;

auto iter = container.begin();
while (iter != container.end())
{
if (shouldRemove(*iter))
{
iter = container.erase(iter);
++numRemoved;
}
else
{
++iter;
}
}

return numRemoved;
}

Class template with variable type of container

Sure you can. This is called a container adapter. std::queue itself is a container adapter and looks like

template<class T, class Container = std::deque<T>> 
class queue
{
//...
};

Doing that though requires you to use something like

std::queue<int, std::vector<int>> foo;

If you want to change the container. If you do not want to have to specify the template type of the container then you can use a template template like

template<class T, template<typename...> class Container = std::queue> 
class Myclass
{
Container<T> cont;
};

and you can use it like

Myclass<int, std::set> foo;

To change it to use a std::set instead of the default std::queue.

Template class with template container

You should use template template parameters:

template<typename T, template <typename, typename> class Container>
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
class MyMultibyteString
{
Container<T, std::allocator<T>> buffer;
// ...
};

This would allow you to write:

MyMultibyteString<int, std::vector> mbs;

Here is a compiling live example. An alternative way of writing the above could be:

template<typename T,
template <typename, typename = std::allocator<T>> class Container>
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
class MyMultibyteString
{
Container<T> buffer; // <== No more need to specify the second argument here
// ...
};

And here is the corresponding live example.

The only thing you have to pay attention to is that the number and type of arguments in the template template parameter declaration must match exactly the number and type of arguments in the definition of the corresponding class template you want to pass as a template argument, regardless of the fact that some of those parameters may have default values.

For instance, the class template std::vector accepts two template parameters (the element type and the allocator type), although the second one has the default value std::allocator<T>. Because of this, you could not write:

template<typename T, template <typename> class Container>
// ^^^^^^^^
// Notice: just one template parameter declared!
class MyMultibyteString
{
Container<T> buffer;
// ...
};

// ...

MyMultibyteString<int, std::vector> mbs; // ERROR!
// ^^^^^^^^^^^
// The std::vector class template accepts *two*
// template parameters (even though the second
// one has a default argument)

This means that you won't be able to write one single class template that can accept both std::set and std::vector as a template template parameter, because unlike std::vector, the std::set class template accepts three template parameters.

conditional type define in c++?

In C++20, you can just use concepts

template<typename Container>
struct B { };

template<typename Container>
requires requires { typename Container::NestedTypeA; }
struct B<Container> {
using NestedType = typename Container::NestedTypeA;
};

template<typename Container>
requires requires { typename Container::NestedTypeB; } &&
(!requires { typename Container::NestedTypeA; })
struct B<Container> {
using NestedType = typename Container::NestedTypeB;
};

template<typename Container>
requires requires { typename Container::NestedTypeC; } &&
(!requires { typename Container::NestedTypeA; }) &&
(!requires { typename Container::NestedTypeB; })
struct B<Container> {
using NestedType = typename Container::NestedTypeC;
};

template<typename Container>
class A : public B<Container> {};

Demo

In C++17, you can use std::void_t to detect the validity of member types.

#include <type_traits>

template<typename Container, typename = void>
constexpr bool HasNestedTypeA = false;
template<typename Container>
constexpr bool HasNestedTypeA<
Container, std::void_t<typename Container::NestedTypeA>> = true;

template<typename Container, typename = void>
constexpr bool HasNestedTypeB = false;
template<typename Container>
constexpr bool HasNestedTypeB<
Container, std::void_t<typename Container::NestedTypeB>> = true;

template<typename Container, typename = void>
constexpr bool HasNestedTypeC = false;
template<typename Container>
constexpr bool HasNestedTypeC<
Container, std::void_t<typename Container::NestedTypeC>> = true;

template<
typename Container,
bool = HasNestedTypeA<Container>,
bool = HasNestedTypeB<Container>,
bool = HasNestedTypeC<Container>>
struct B { };

template<typename Container>
struct B<Container, false, false, false> {};

template<typename Container, bool B1, bool B2>
struct B<Container, true, B1, B2> {
using NestedType = typename Container::NestedTypeA;
};

template<typename Container, bool B1>
struct B<Container, false, true, B1> {
using NestedType = typename Container::NestedTypeB;
};

template<typename Container>
struct B<Container, false, false, true> {
using NestedType = typename Container::NestedTypeC;
};

template<typename Container>
class A : public B<Container> {};

Demo

How to define a template class function with nested type of the template argument in its signature outside the class

You have to use typename

template <class ElementType>
typename AklMemoryPool<ElementType>::RetrieveType
AklMemoryPool<ElementType>::Retrieve(unsigned int offset) const
{
...
}

or use the type in a correct context (since C++11)

template <class ElementType>
auto
AklMemoryPool<ElementType>::Retrieve(unsigned int offset) const
-> RetrieveType
{
...
}

How to store templated objects of different type in container?

Different instantiations of a class template are completely unrelated types, so you cannot have a container that directly stores them.

You have a few options:

  1. Keep a collection of pointers to some base class that your class template inherits from:
class Base
{
virtual ~Base {}
virtual void someMethod() const = 0;
};

template <typename T>
class MyClass : public Base
{
void someMethod() const
{
// stuff
}
};

int main()
{
std::vector<std::unique_ptr<Base>> objs;
objs.push_back(std::make_unique<MyClass<int>>());
objs.push_back(std::make_unique<MyClass<std::string>>());

for (auto& i : objs) {
i->someMethod();
}
}

This is a fairly simple approach, but it incurs a bit of runtime overhead with dynamic allocation and RTTI. Note also that someMethod can't return T, since it's a method on a parent class that doesn't know what T is.


  1. Use some sort of type-erased wrapper like boost::any (or the forthcoming std::any in C++17).
#include <any>
#include <string>
#include <vector>

template <typename T>
class MyClass {
public:
T someMethod() const {
// stuff
return {};
}
};

void someFunctionThatTakesInt(int i) {}
void someFunctionThatTakesString(std::string s) {}

int main() {
std::vector<std::any> objs;
objs.push_back(MyClass<int>());
objs.push_back(MyClass<std::string>());

for (const auto& i : objs) {
if (i.type() == typeid(MyClass<int>)) {
auto& mc = std::any_cast<const MyClass<int>&>(i);
someFunctionThatTakesInt(mc.someMethod());
} else if (i.type() == typeid(MyClass<std::string>)) {
auto& mc = std::any_cast<const MyClass<std::string>&>(i);
someFunctionThatTakesString(mc.someMethod());
}
}
}

This approach means that you can have someMethod return T, but makes it much harder to handle retrieving objects from the vector because you have to figure out what type they are before you can do anything with them (you're essentially rolling your own RTTI).


  1. Don't.

Rethink why you need this in the first place. Maybe another approach could work better. Maybe something with callbacks or visitors. I don't know your objective here, so I can't really say what's appropriate.

How can I write a template function that takes a generic C++ standard library container

You should declare T as template template parameter to indicate that it's a template-name (and needs arguments to be instantiated), e.g.

template<template <typename...> class T> 
void foo(const T<bar>&, const T<double>&);

LIVE



Related Topics



Leave a reply



Submit