I've got a template function in VS2013 designed to perform a "deep copy" of whatever object given to it. One overload is for trivial types just calls operator=. But I also have an overload designed to work for vectors of shared_ptr to my own Shape-class objects, which can only be duplicated by calling a clone() member function.
struct Shape { virtual std::shared_ptr<Shape> clone() const = 0; };
struct Rectangle : public Shape { virtual std::shared_ptr<Shape> clone() const override; };
So I've got this overload and the compiler chooses it just fine
template<class SHP>
inline std::vector<std::shared_ptr<SHP>> deep_copy(
const std::vector<std::shared_ptr<SHP>>& val,
typename std::enable_if<std::is_base_of<Shape, SHP>::value>::type** = nullptr)
{
// ... blah blah blah
}
std::vector<std::shared_ptr<Rectangle>> objects;
auto objects2 = deep_copy(objects);
Then I wanted to change this to take ANY non-keyed collection (e.g. list) of shared_ptr. OK, no problem, I actually managed that...
template<class COLL>
inline COLL deep_copy(const COLL& val,
typename std::enable_if<std::is_base_of<Shape, typename COLL::value_type::element_type>::value>::type** = nullptr)
But this syntax doesn't really ensure that the collection contains shared_ptr. It merely ensures that its value_type has a nested element_type that is some sort of Shape
So my question is, What would be the syntax for ensuring that the contents of the collection are actually std::shared_ptr to something derived from Shape?
I've made serveral attempts at this using template template parameters but I keep screwing it up.
Let's start with a concept for testing if a T
is a shared_ptr
:
template<typename T>
struct is_shared_ptr : std::false_type{};
template<typename T>
struct is_shared_ptr<std::shared_ptr<T>> : std::true_type{};
Now something like
std::cout << std::boolalpha << is_shared_ptr<std::shared_ptr<Shape>>::value << std::endl;
Will output
true
Next, if something gives a true_type
as a result of using that concept, we'd want to next check its element type
:
template<typename T>
using element_t = typename std::decay_t<T>::element_type;
To see if the returned type derives from Shape
.
Let's create another helper alias to grab the value_type
off a collection:
template<typename T>
using collection_vt = typename std::decay_t<T>::value_type;
And now we can combine these concepts into one really gross looking one:
template<template<class...> class C, typename U, typename... A>
auto DoTheThing(const C<U, A...>& _collection) -> std::enable_if_t<
is_shared_ptr<collection_vt<decltype(_collection)>>::value &&
std::is_base_of<Shape, element_t<collection_vt<decltype(_collection)>>>::value
>
{
std::cout << _collection.size() << std::endl;
}
The initial template arguments are for accepting a relatively generic container. Then we use a trailing return type to first ensure that value_type
is a shared_ptr
, and then afterwards also check that element_type
of the shared_ptr
derives from Shape
. Since, upon success, the type from enable_if
is void
, the return type of the function becomes void.
Here's the test:
std::vector<std::shared_ptr<Shape>> vec1;
DoTheThing(vec1); // success
std::vector<Wrapper<Shape>> vec2;
//DoTheThing(vec2); // error
std::vector<std::shared_ptr<int>> vec3;
//DoTheThing(vec3); // error
std::list<std::shared_ptr<Shape>> vec4;
DoTheThing(vec4); // success