Страницы

Поиск по вопросам

суббота, 7 декабря 2019 г.

Дополнительная Специализация конструктора шаблонного класса

#cpp #шаблоны_с++ #function_overloading


Пытаюсь реализовать свой дек для обучения.
Встал на следующем моменте

template  class Deque
{
private:
    enum { defaultSize = 25, len = 5 };
    void dealloc ( );
    int alloc   ( const unsigned & = defaultSize, const T & = T() );
    T ** array;
    pair xy;
...

public:
    Deque ( const unsigned &, const T & = T() );                       //1
    template  Deque (Iterator _first, Iterator _last); //2
    Deque ( ); 
    virtual ~Deque();


};


В main.cpp идёт создание дека:

Deque test1(25, -1);


Все хорошо если нет 2-го конструктора.
Второй конструктор "перебивает" действие первого. Почему так происходит я не понимаю,
ведь в конструктор передаются типы const unsigned и const int и как я понимаю компилятору
следовало бы направить этот путь к первому конструктору, но он упорно использует второй...

Подскажите пожалуйста что не так.



UPDATE:

Ещё раз спасибо, всем кто ответил, разобрался и узнал новое. Позволю себе добавить
некоторые уточнения, которые узнал, и которые, возможно покажутся интересными и другим.
В частности, это уточнение комментария:
"
std::enable_if пишется за 10 минут на C++98, просто с C++11 это часть стандарта.
SFINAE доступно довольно давно, поэтому, наиболее вероятно, как-то так и было сделано
раньше. 
"

Поехали: в файле /usr/include/c++/4.9/bits/stl_deque.h, где-то в районе 899 строки будет:

#if __cplusplus >= 201103L
      deque(size_type __n, const value_type& __value,
            const allocator_type& __a = allocator_type())
      : _Base(__a, __n)
      { _M_fill_initialize(__value); }
#else
      explicit
      deque(size_type __n, const value_type& __value = value_type(),
            const allocator_type& __a = allocator_type())
      : _Base(__a, __n)
      { _M_fill_initialize(__value); } /*--------------- #1 */
#endif


#if __cplusplus >= 201103L
      template>
        deque(_InputIterator __first, _InputIterator __last,
              const allocator_type& __a = allocator_type())
        : _Base(__a)
        { _M_initialize_dispatch(__first, __last, __false_type()); }
#else
      template
        deque(_InputIterator __first, _InputIterator __last,
              const allocator_type& __a = allocator_type())
        : _Base(__a)
        {
          // Check whether it's an integral type.  If so, it's not an iterator.
          typedef typename std::__is_integer<_InputIterator>::__type _Integral;
          _M_initialize_dispatch(__first, __last, _Integral());
        } /*--------------- #2 */
#endif


Т.е. видно что в 11-м стандарте (как и описывалось в сообщениях выше) используется
подход метапрограммирования и шаблон, который определяет «подключать» ли данный вызов
- _RequireInputIter

В 98м же Си++ используется вызов на прямую, без таких вот конструкций, типа _RequireInputIter,
как я и пытался сделать изначально, следуя определению интерфейса дека.

Но наткнулся на то, что (как и было выше мне объяснено) компилятор в общем случае
воспринимает конструктор

templatedeque(_InputIterator _first, _InputIterator __last,
const allocator_type& __a = allocator_type())


как более предпочтительный (т.к. из за наличия шаблонов, в него можно «всунуть» любой
тип сразу, не задействуя преобразования типов) нежели конструктор 

deque(size_type __n, const value_type& __value = value_type(), const allocator_type&
__a = allocator_type())


Но и, собственно говоря, в реализации STL для си++98 все так и получается, как получалось
у меня на момент написания первого сообщения в этой теме..

Такой вызов 

std::deque dq (10,-1)


идёт непосредственно к конструктору ----- #2

А вот такой

std::deque dq (10u,-1)


, к конструктору ----- #1

Т.е. можно говорит, что до появления std::enable_if - конструктор ----- #1 почти
не использовался, а лишь служил описанием интерфейса.

А его работу исполнял ----- #2, используя для своего «двойного» поведения метод 

_M_initialize_dispatch(__first, __last, _Integral());


Который, в случае если вызов объекта _Integral() имеет тип __true_type - вызывает
ф-ю _M_initialize_dispatch, которая, рассмартивает первые два параметра как простые
типы - первый тип размера, второй тип заполняемого значения,

Если же при инстанцировании 

__is_integer::__type


InputIterator не попадает ни в одну из перегруженных для __is_integer специализаций,
то объект _Integral() имеет тип __false_type

И в таком случае используется перегруженная версия 

_M_initialize_dispatch


, которая, переданные в неё параметры воспринимает как итераторы а не базовые типы.

И вот, таким вот образом, один конструктор, (с другим интрефейсом и как бы созданный
совсем для другого) эмулирует действия первого

Убедиться в этом можно, заккоментив stl_deque.h пару строк 

  template
    void
    _M_initialize_dispatch(_Integer __n, _Integer __x, __true_type)
    {
      //_M_initialize_map(static_cast(__n));
      //_M_fill_initialize(__x);
    }


и выполнив код 

std::deque dq (10,-1);
std::copy ( dq.begin(), dq.end(), std::ostream_iterator(std::cout, " "));


собранный для -std=c++11 и -std=c++98.
    


Ответы

Ответ 1



Второй конструктор побеждает потому, что для первого требуется преобразование int -> unsigned int, тогда как для второго никаких преобразований не требуется. Самым простым вариантом избавления от этой проблемы, будет следующий: template ::value, void>::type> Deque (Iterator _first, Iterator _last); //2 Но это очень грубое решение(я имею в виду проверку is_integral), желательно включать этот конструктор только для итераторов, но для этого нужно знать, что является для Вас итератором.

Комментариев нет:

Отправить комментарий