使用模板模板参数和 enable_if 正确选择重载

Getting overload selection right with template template params and enable_if

我在 VS2013 中有一个模板函数,旨在对提供给它的任何对象执行 "deep copy"。一种重载是针对普通类型的,只需调用 operator=。但是我也有一个重载设计用于将 shared_ptr 的向量用于我自己的 Shape-class 对象,它只能通过调用 clone() 成员函数来复制。

struct Shape { virtual std::shared_ptr<Shape> clone() const = 0; };
struct Rectangle : public Shape { virtual std::shared_ptr<Shape> clone() const override; };

所以我有这个重载,编译器选择它就好了

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);

然后我想更改它以获取 shared_ptr 的任何非键控集合(例如列表)。好的,没问题,我实际上做到了...

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)

但此语法确实 无法确保集合包含shared_ptr。它只是确保它的 value_type 有一个嵌套的 element_type 是某种形状

所以我的问题是,确保集合内容实际上是 std::shared_ptr 派生自 Shape 的语法是什么?

我已经使用模板模板参数对此进行了多次尝试,但我总是搞砸了。

让我们从测试 T 是否为 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{};

现在像

std::cout << std::boolalpha << is_shared_ptr<std::shared_ptr<Shape>>::value << std::endl;

会输出

true

接下来,如果某物由于使用该概念而给出 true_type,我们接下来要检查它的 element type:

template<typename T>
using element_t = typename std::decay_t<T>::element_type;

查看 returned 类型是否派生自 Shape

让我们创建另一个辅助别名来从集合中获取 value_type

template<typename T>
using collection_vt = typename std::decay_t<T>::value_type;

现在我们可以将这些概念组合成一个看起来非常粗糙的概念:

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;
}

初始模板参数用于接受相对通用的容器。然后我们使用尾随 return 类型首先确保 value_typeshared_ptr,然后还要检查 shared_ptrelement_type 是否来自 Shape。因为,一旦成功,来自 enable_if 的类型是 void,函数的 return 类型变为无效。

Live Demo

这是测试:

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