Перегрузка функции шаблона вызвана не так, как ожидалось - PullRequest
2 голосов
/ 25 января 2011

Моя ситуация следующая:

У меня есть шаблонная оболочка, которая обрабатывает ситуацию значений и обнуляемых объектов без необходимости вручную обрабатывать указатель или даже new. Это в основном сводится к этому:

struct null_t
{
  // just a dummy
};
static const null_t null;

template<class T> class nullable
{
public:
  nullable()
    : _t(new T())
  {}

  nullable(const nullable<T>& source)
    : _t(source == null ? 0 : new T(*source._t))
  {}

  nullable(const null_t& null)
    : _t(0)
  {}

  nullable(const T& t)
    : _t(new T(t))
  {}

  ~nullable()
  {
    delete _t;
  }

  /* comparison and assignment operators */

  const T& operator*() const
  {
    assert(_t != 0);
    return *_t;
  }

  operator T&()
  {
    assert(_t != 0);
    return *_t;
  }

  operator const T&() const
  {
    assert(_t != 0);
    return *_t;
  }
private:
  T* _t;
};

Теперь, используя операторы сравнения, я могу проверить фиктивный код null_t, чтобы увидеть, установлено ли для него значение null, прежде чем пытаться получить значение или передать его в функцию, которая требует это значение и выполняет автоматическое преобразование. .

Этот урок довольно долго служил мне, пока я не наткнулся на проблему. У меня есть класс данных, содержащий некоторые структуры, которые все будут выводиться в файл (в данном случае XML).

Так что у меня есть такие функции

xml_iterator Add(xml_iterator parent, const char* name,
                 const MyDataStruct1& value);

xml_iterator Add(xml_iterator parent, const char* name,
                 const MyDataStruct2& value);

каждый из которых заполняет XML-DOM соответствующими данными. Это также работает правильно.

Теперь, однако, некоторые из этих структур являются необязательными, которые в коде будут объявлены как

nullable<MyDataStruct3> SomeOptionalData;

И для этого случая я сделал перегрузку шаблона:

template<class T>
xml_iterator Add(xml_iterator parent, const char* name,
                 const nullable<T>& value)
{
  if (value != null)  return Add(parent, name, *value);
  else                return parent;
}

В моих модульных тестах компилятор, как и ожидалось, всегда предпочитал выбирать эту функцию шаблона, где бы значение или структура не были заключены в nullable<T>.

Если, однако, я использую вышеупомянутый класс данных (который экспортируется в свою собственную DLL), по какой-то причине самый первый раз, когда должна вызываться последняя функция шаблона, вместо этого происходит автоматическое преобразование из nullable<T> в соответствующий тип T сделано, полностью обходя функцию, предназначенную для обработки этого случая. Как я уже говорил выше - все модульные тесты прошли на 100% нормально, MSVC 2005 в режиме отладки собирает как тесты, так и исполняемый файл, вызывающий код, - эту проблему определенно нельзя отнести к различиям компилятора.

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

Ответы [ 2 ]

0 голосов
/ 22 февраля 2011

Ну, так как никакого реального ответа пока не найдено, я сделал обходной путь. По сути, я поместил вышеупомянутые Add функции в отдельное пространство имен detail и добавил две функции-оболочки шаблона:

    template<class T>
    xml_iterator Add(xml_iterator parent, const char* name,
                     const T& value)
    {
      return detail::Add(parent, name, value);
    }

    template<class T>
    xml_iterator Add(xml_iterator parent, const char* name,
                     const nullable<T>& value)
    {
      return value != null ? detail::Add(parent, name, *value) : parent;
    }

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

0 голосов
/ 25 января 2011

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

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

Я бы сделал следующее:

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

    template<class T> class nullable
    
    {
      public:
        nullable() 
           : _t(new T())
        {}
    
    
    explicit nullable(const nullable<T>& source)
       : _t(source == null ? 0 : new T(*source._t))
    {}
    
    explicit nullable(const null_t& null)
        : _t(0)
      {}
    
      explicit nullable(const T& t)
        : _t(new T(t))
      {}
    // rest
    };
    
  • Заменить оператор T & преобразования на именованные функции. Используйте ref () для non-const и cref () для const.

Я бы также закончил урок с

  • оператор присваивания (необходим для правила 3)
  • оператор-> две перегрузки при распространении константы.

Если вы планируете использовать это для C ++ 0x, также скопируйте и назначьте значение r, что полезно в этом случае.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...