Правильный выбор перегрузки с параметрами шаблона шаблона и enable_if

У меня есть функция шаблона в VS2013, предназначенная для выполнения «глубокой копии» любого переданного ей объекта. Одна перегрузка для тривиальных типов просто вызывает оператор =. Но у меня также есть перегрузка, предназначенная для работы с векторами shared_ptr в мои собственные объекты класса Shape, которые можно дублировать только путем вызова функции-члена 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, который является своего рода Shape

Итак, мой вопрос: Каким должен быть синтаксис для обеспечения того, чтобы содержимое коллекции на самом деле было std::shared_ptr чем-то производным от Shape?

Я предпринял несколько попыток сделать это, используя параметры шаблона шаблона, но я все время ошибаюсь.


person Joe    schedule 01.02.2016    source источник


Ответы (1)


Давайте начнем с концепции проверки того, является ли 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_type в результате использования этой концепции, мы хотели бы затем проверить его element type:

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

Чтобы узнать, происходит ли возвращаемый тип от 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;
}

Первоначальные аргументы шаблона предназначены для принятия относительно универсального контейнера. Затем мы используем завершающий возвращаемый тип, чтобы сначала убедиться, что value_type является shared_ptr, а затем также проверить, что element_type из shared_ptr происходит от Shape. Поскольку в случае успеха тип из enable_if равен void, возвращаемый тип функции становится недействительным.

Текущая демонстрация

Вот тест:

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
person AndyG    schedule 01.02.2016
comment
Спасибо, мне это нравится. Я переварю это и попробую. Я не упомянул, что мне также нужно строить с помощью VS2012 (поэтому у меня нет шаблонов с переменным числом аргументов), но я могу обойти это. Забавно, я уже определял свои собственные предикаты (например, if_shape), поэтому не могу поверить, что мне никогда не приходило в голову определить один для is_shared_ptr. Просто для проверки: это удобно для SFINAE при наличии других перегрузок, да? У меня есть еще одна рабочая перегрузка enable_if-ed только для одного shared_ptr‹Shape›. Хотите быть уверенным, что вызов этого заставит компилятор отбросить этот, а не жаловаться. - person Joe; 02.02.2016
comment
Да, я так думаю. Если у вас есть перегрузка, которая точно соответствует типу ввода, ее следует выбрать. - person AndyG; 02.02.2016