2017-01-20 4 views
2

У меня есть приятный и маленький общий характер типа Array, который до сих пор подходит всем моим потребностям.Устранение конструктора C++ и неопределенность вызова

template <typename T> 
class Array 
{ 
    ... 

public: 
    int Data; // custom value 
    virtual void InitData() { Data = 0; } 

    Array(const Array& array); 
    template <typename U, typename = std::enable_if<std::is_same<U, T>::value, U>> Array(const Array<U>& array); 
    template <typename... Ts> Array(const Ts&... items); 

    void Add(const T& item); 
    template <typename... Ts> void Add(const T& item, const Ts&... rest); 
    void Add(const Array& array); 
} 

template <typename... Ts> Array(const Ts&... items); позволяет мне делать Array<T> array = { ... }, и назначать и возвращать {...} списки инициализатора. Поскольку ни один из конструкторов не является явным, это невероятно удобно, но также является причиной, по которой я сейчас застрял.

Я хотел бы иметь возможность добавлять что-либо «разумное» к массивам. Мой главный случай использования прямо сейчас:

using Curve = Array<float2>; 

class Poly : public Array<float2> { using Array::Array; void InitData() override { Data = 1; } }; 
class Curve2 : public Array<float2> { using Array::Array; void InitData() override { Data = 2; } }; 
class Curve3 : public Array<float2> { using Array::Array; void InitData() override { Data = 3; } }; 

std::is_same<> материала выше специально, чтобы иметь возможность рассматривать все кривые, как то же самое, но не то же самое: типы кривых разной степени, и все хорошо «статический напечатано ", поэтому все, что я делаю в функции вроде DrawCurve(const Curve&), - это проверить степень, а затем предпринять соответствующие действия. Curve - хороший псевдоним для Array, Curve2 и т. Д. - это специализации по степени специализации. Он работает очень хорошо.

Когда я вхожу в конструкцию кривой, у меня обычно есть объект кривой, к которому я добавляю либо точки, либо сегменты кривой. Так что я хотел бы быть в состоянии сделать:

Curve3 curve; 
curve.Add(float2()); // ambiguity 
curve.Add(Array<float2>()); 

К сожалению, я получаю неоднозначность здесь, когда я называю добавить, потому что Add() будет принимать либо float2 или Array<float2>, который прекрасно работает, но массив имеет неявный конструктор template <typename... Ts> Array(const Ts&...), который может принимать float2 в качестве аргумента. Таким образом, неоднозначность между

Array::Add(float2()); // and 
Array::Add(Array<float2>(float2())); 

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

template <typename A, typename = std::enable_if<std::is_same<A, Array>::value, A>> 
void Add(const Array& array); 

Но тогда я получаю новые ошибки преобразования из Curve3 в float2 и т.д., и это становится беспорядком.

Моя надежда заключается в том, что где-то в глубине шаблонов или других положительных героев C++ лежит простое решение, которое именно то, что мне нужно. (Да, я знаю, что могу просто переименовать методы :: AddItem() и :: AddArray(), и проблема закончится через секунду, но я не хочу этого, потому что в конце концов я хочу удвоить все это с помощью += а затем в основном просто использовать.

Любые идеи?

+0

только что подключил код в [здесь] (http://coliru.stacked-crooked.com/a/beaea40305a2119f), похоже, что между этими двумя добавочными номерами не существует двусмысленности. –

ответ

3

Обратите внимание, что вы хотите

template <typename... Ts> Array(const Ts&... items); 

будет использоваться только если пакет параметр содержит по меньшей мере один элемент, и тип этого элемента не является Array. Если пакет параметров пуст, он становится конструктором по умолчанию, поэтому давайте рассмотрим этот случай отдельно. ad и явно определить конструктор по умолчанию, если вам нужно, и заставить его делать то, что ему нужно сделать; теперь мы можем исключить эту возможность из этого варианта использования и продвигаться вперед.

Получив, что из пути, что вы хотите сделать здесь, чтобы использовать этот конструктор только тогда, когда она имеет один аргумент:

template <typename T1, typename... Ts> Array(const T1 &t1, const Ts&... items); 

Вы должны будете изменить этот конструктор, чтобы использовать явный t1, в дополнение к существующему пакету параметров, который он использует. Это должно быть достаточно простым, но этого будет недостаточно. Там все еще двусмысленность. Вы хотите, чтобы этот конструктор был выбран только в том случае, если T1 не является Array.

Возможно, есть способ придумать что-то запутанное и наполнить его одним одиночным std::enable_if и засунуть его в этот шаблон. Но для ясности и простоты, я хотел бы использовать вспомогательный класс:

template<typename T> class is_not_array : public std::true_type {}; 

template<typename T> 
class is_not_array<Array<T>> : public std::false_type {}; 

А затем добавить простой std::enable_if в шаблон этого конструктора использовать SFINAE, чтобы выбрать только этот конструктор, когда его первый параметр шаблона не является Array, аналогичный как вы используете std::enable_if уже, в другом конструкторе.

Это должно устранить все двусмысленности. Этот конструктор следует выбирать только по меньшей мере с одним параметром шаблона, который не является Array, с помощью этого вспомогательного класса. Когда это Array, этот конструктор не будет разрешимым, и этот случай перейдет к другому конструктору.

Я бы также предложил использовать универсальные ссылки в шаблонах, а не const T & s.

+0

Спасибо, что решил! Метапрограммирование шаблонов по-прежнему очень загадочно для меня. :) – Alex

Смежные вопросы