Обходной путь для специализированного шаблона для функции-члена, когда шаблонный класс не специализирован - PullRequest
0 голосов
/ 26 апреля 2011

Я создаю класс структуры данных с интерфейсом, похожим на std, и реализую различные итераторы для структуры данных.

Концептуально, я хотел бы сделать что-то вроде этого:

template <class DataT>
class DataStructure
{
protected:
    DataT& Data;
public:
    DataStructure(DataT& data) : Data(data) {}
    class BaseIterator
    {
    public:
        BaseIterator()
        {
            cout<<"BaseIterator"<<endl;
        }
    };

    class DerrivedIterator1 : public BaseIterator
    {
    public:
        DerrivedIterator1()
        {
            cout<<"DerrivedIterator1"<<endl;
        }
    };

    class DerrivedIterator2 : public BaseIterator
    {
    public:
        DerrivedIterator2()
        {
            cout<<"DerrivedIterator2"<<endl;
        }
    };

    template<class IterT>
    IterT Begin()
    {
        //none-specialized implementation. Possibly throw exception
    }

    template<>
    DerrivedIterator1 Begin<DerrivedIterator1>()
    {
        //Find beginning for DerrivedIterator1
    }

    template<>
    DerrivedIterator2 Begin<DerrivedIterator2>()
    {
        //Find beginning for DerrivedIterator1
    }
};

Но это, конечно, не компилируется, поскольку C ++ не позволяет специализировать функции-члены шаблона в неспециализированных контейнерах шаблонов.

Очевидным обходным решением является, конечно, объявление 2 разных функций: Begin_Iterator1 и Begin_Iterator2и покончим с этим.Но я ищу обходной путь, который не меняет интерфейс.

Любые идеи?

Редактировать: я забыл упомянуть, что это для назначения HW и так повысить и даже стандартныйне вариант.

Ответы [ 2 ]

1 голос
/ 26 апреля 2011

Шаблоны функций не могут быть специализированы на C ++, точка.

Неважно, являются ли они членами шаблона, специализация шаблонов функций не допускается. Обычно при использовании типов аргументов для вывода аргументов шаблона перегрузка выполняет ту же специализацию, поэтому специализация для функций (и связанная с этим дополнительная сложность в разрешении перегрузки и т. Д.) Не считалась необходимой.

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

DataStructure::DerivedIterator1 i = dataStructure.Begin();

будет не работать так, как вы написали код, поскольку вывод типа, как и разрешение перегрузки, выполняется только для аргументов, а не для ожидаемого возвращаемого значения. Вы должны написать:

DataStructure::DerivedIterator1 i = dataStructure.Begin<DataStructure::DerivedIterator1>();

и это имеет нулевое преимущество по сравнению с:

DataStructure::DerivedIterator1 i = dataStructure.BeginIterator1();

Однако первое выражение может быть сделано для работы с некоторыми волшебствами. Сначала вы должны определить BeginIterator1 и BeginIterator2, а затем сделать временный выбор, чтобы позднее решить, какой из них построить:

class DataStructure {
    ...
    class BeginIteratorConstructor {
        DataStructure &dataStructure;
    public:
        BeginIteratorConstructor(DataStructure &ds) : dataStructure(ds) {}
        operator DerivedIterator1() { return dataStructure.BeginIterator1(); }
        operator DerivedIterator2() { return dataStructure.BeginIterator2(); }
    };
    BeginIteratorConstructor Begin() { return BeginIteratorConstructor(*this); }
    ...
};

Теперь dataStructure.Begin() вернет временное что-то, что вызовет BeginIterator1, если вы приведете его к DerivedIterator1 или вызову BeginIterator2, когда вы приведете к DerivedIterator2. Если вы передадите его чему-то, где компилятор не сможет решить, к какому из них привести данные, он умрет либо из-за неоднозначной перегрузки, либо из-за того, что BeginIteratorConstructor на самом деле не является итератором, и вам придется явно приводить его.

(Вы должны тщательно сделать как можно больше BeginIteratorConstructor приватных данных, но я не уверен, насколько далеко компилятор позволит вам зайти, поэтому вам придется немного поэкспериментировать)

0 голосов
/ 26 апреля 2011

Вы можете использовать систему тегов, которая избавит вас от частично специализированных функций внутри шаблонов классов:

struct base_iter_tag{};
struct der1_iter_tag{};
struct der2_iter_tag{};

template<class T>
struct iter_type;

template<>
struct iter_type<BaseIterator>{
  typedef base_iter_tag tag;
};

template<>
struct iter_type<DerivedIterator1>{
  typedef der1_iter_tag tag;
};

template<>
struct iter_type<DerivedIterator2>{
  typedef der2_iter_tag tag;
};

template<class IterT>
IterT Begin(){
  return DoBegin(typename iter_type<IterT>::tag());
}

BaseIterator DoBegin(base_iter_tag){
  // ...
}

DerivedIterator1 DoBegin(der1_iter_tag){
  // ...
}

DerivedIterator2 DoBegin(der2_iter_tag){
  // ...
}

По сути, это то, что стандартная библиотека делает с iterator_traits<T>::iterator_category и перегруженными функциями в зависимости от категории (например, forward_iterator_tag, random_access_iterator_tag и т. Д.).

...