На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Модераторы: Qraizer, Hsilgos
  
> Не компилируется кусок с template и operator
    Добрый день, уважаемые программисты. Не так давно я начал перелазить с Delphi на C++. Поначалу меня охватила эйфория, когда я понял, какие ништяки могу писать, но потом она сошла, так как эти самые ништяки никак не компилятся. Код:

    ExpandedWrap disabled
      //LazyString.h
       
      namespace Lazy{
          template<typename T> class BasicString
          {
          public:
              BasicString operator= (const BasicString& right);
              friend BasicString operator+(const BasicString& left, const BasicString& right);
              friend bool operator==(const BasicString& left, const BasicString& right);
              friend bool operator!=(const BasicString& left, const BasicString& right);
          }
      }


    ExpandedWrap disabled
      //LazyString.cpp
       
      #include "LazyString.h"
       
      template<typename T> Lazy::BasicString<T>  Lazy::BasicString<T>::operator= (const Lazy::BasicString<T>& right)
      {};
       
      template<typename T> Lazy::BasicString<T> Lazy::operator+(const Lazy::BasicString<T> & left, const Lazy::BasicString<T> & right)
      {};
       
      template<typename T> bool Lazy::operator==(const Lazy::BasicString<T> &left, const Lazy::BasicString<T> &right)
      {};
       
      template<typename T> bool Lazy::operator!=(const Lazy::BasicString<T>& left, const Lazy::BasicString<T>& right)
      {};


    Эти функции сгенерировала VS. Без проблем только присваивание, на остальных ошибки типа "+ не явлется членом Lazy". Убираю "Lazy::" - VS подчёркивает функции зелёным, говорит, что нет определения, а при компиляции "ссылка на неразрешенный символ". Я так понимаю, компилятор не находит определения.

    Думаю, можно было бы объявить и определить в одном месте, но хотелось бы сделать как положено. Подскажите, пожалуйста, как.
      Не считая отсутствующие return в операторах, что, как я подозреваю, является всё-таки не ошибкой, а следствием чрезмерного упрощения примера, operator= должен возвращать ссылку, а не значение, иначе каждый раз будет вызываться конструктор копии, да и семантический смысл operator= изменится.
      Но это не главное. С друзьями ты явно не дружишь. Не ты первый, не ты, увы, последний, я уверен.

      Добавлено
      По порядку. Я практически уверен, что у тебя компилятор видит объявление вроде вот того BasicString<T>::operator+() впервые внутри класса. Т.е. до этого никаких их объявлений не было. Ведь так? Инфа 100%? Давай, ставь себя на место компилятора:
      • вижу сигнатуру operator+() внутри области видимости BasicString<>; это не может быть метод, т.к. friend, значит это свободная функция;
      • ищу этот символ в своих таблицах; т.к. это не метод, внутри области видимости BasicString<> искать не надо, начинаю с окаймляющей;
      • в области видимости Lazy символ не находится, перехожу к окаймляющей;
      • в глобальной области видимости символ не находится, поиск окончен;
      • вывод: это объявление другом одновременно является и просто первым объявлением этого operator+().
      Сам догадаешься, что из этого следует?

      Добавлено
      Если не догадался, продолжаем. Полная сигнатура оператора формируется на основе текущей области видимости, но т.к. это область видимости класса, но оператор друг, а следовательно не метод, то в его сигнатуру не будет входить область видимости BasicString<>, но будет входить как некое имя, квалифицирующее его параметры. Т.е. фактически ты получишь что-то типа
      ExpandedWrap disabled
        template<typename T> BasicString<T> operator+(const BasicString<T>& left, const BasicString<T>& right);
      но только "что-то типа", а на самом деле нифига подобного. Вот эта "что-то типа" являлось бы шаблонной функцией, однако friend в классе определён без любого template <>. Ты его там видишь? Я вот не вижу. И компилятор тоже не увидит. Не, он конечно есть, перед классом. Вот в этом-то и прикол: он относится к классу BasicString, а не к его другу operator+(), поэтому друг нешаблонный, однако зависит от параметра шаблона класса.

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

      Добавлено
      Да, с друзьями вот так. Объявление сущности, чтобы она стала известна компилятору, и объявление её другом другой сущности, чтобы компилятор знал, что у неё есть особые полномочия – это разные вещи. И хоть они могут быть совмещены в одном месте, лучше этого не делать. Ведь по сути твой оператор, хоть и нешаблонный, всё равно зависит от параметра шаблона BasicString, а значит каждый BasicString с уникальным T, получит собственного друга operator+(). И их будет много, как было бы много и шаблонных operator+().

      Добавлено
      Как решить проблему. Тут два способа. Вот простой.
      Совместить определение с объявлением:
      ExpandedWrap disabled
        namespace Lazy{
            template<typename T> class BasicString
            {
        /* ... */
                friend BasicString operator+(const BasicString& left, const BasicString& right)
                {
                    /* ... */
                    return /* ...*/;
                }
        /* ... */
            };
        }
      Только так ты можешь дать определение этому магически нешаблонному "шаблонному" другу, других способов нет. Но это как бы не то решение, которое тебя интересует, ведь так? Тогда более сложное, но и более правильное.
      Разделить первое объявление operator+ и его объявление другом:
      ExpandedWrap disabled
        namespace Lazy
        {
            // объявление шаблонного класса, без него не скомпилится последующий прототип
            template<typename T> class BasicString;
            // объявление будущего друга
            template<typename T>
            BasicString<T> operator+(const BasicString<T>& left, const BasicString<T>& right);
         
            // определение шаблонного класса
            template<typename T> class BasicString
            {
        /* ... */
                // ранее объявленный шаблонный оператор объявить другом BasicString
                friend BasicString operator+<T>(const BasicString& left, const BasicString& right);
        /* ... */
            };
        }
      Вот теперь всё оформлено как надо. Мы имеет обычный шаблонный оператор, объявленный в области видимости пространства имён. Теперь ничто не мешает нам его определить нормальным образом, и твои определения заработают. Так же мы его объявили другом, причём обрати внимание на <T> в конце его имени. Именно так мы ссылаемся, во-первых, на ранее объявленную и уже известную компилятору сущность, во-вторых, ровно на ту его конкретизацию, которая совпадает с конкретизацией BasicString по шаблонному параметру.

      Добавлено
      Теперь компилятор работает так:
      • вижу сигнатуру operator+() внутри области видимости BasicString<>; это не может быть метод, т.к. friend, значит это свободная функция;
      • ищу этот символ в своих таблицах; т.к. это не метод, внутри области видимости BasicString<> искать не надо, начинаю с окаймляющей;
      • в области видимости Lazy символ находится, стоп поиску, к окаймляющей области видимости не переходим;
      • найденный символ является шаблонной функцией, выполняется попытка вывести его шаблонные параметры; попытка успешна (указана явно в объявлении другом);
      • вывод: это объявление объявляет Lazy::operator+<T>() другом Lazy::BasicString<T> при условии совпадения T.


      Добавлено
      Цитата Qraizer @
      Ты ниже пытаешься его определить, но это не тот operator(), который вроде бы друг, т.к. он шаблонный, а тебе нужно определить его как нешаблонный. В результате компилятор видит некое определение, но оно нигде в программе не используется, т.к. вызывается не оно.
      Пожалуй, надо пояснить. В Стандарте чёрным по-англицки написано, что при прочих равных условиях нешаблонные сущности имеют приоритет при разрешении перегрузки. Тут как раз такая ситуация: у компилятора две сигнатуры, одна суть нешаблонный "друг", вторая обычная шаблонная функция. Перегрузку молча выигрывает друг, для которого нет определения. Шаблонная всегда будет проигрывать, поэтому несмотря на имеющееся определение, она бесполезна.
        Цитата k.sovailo @
        Без проблем только присваивание, на остальных ошибки типа "+ не явлется членом Lazy". Убираю "Lazy::" - VS подчёркивает функции зелёным, говорит, что нет определения, а при компиляции "ссылка на неразрешенный символ".

        А это точно необходимо, чтобы функции
        ExpandedWrap disabled
                
                  friend BasicString operator+(const BasicString& left, const BasicString& right);
                  friend bool operator==(const BasicString& left, const BasicString& right);
                  friend bool operator!=(const BasicString& left, const BasicString& right);

        обязательно имели доступ к закрытым членам класса "BasicString" ?
        Конечно, технология использования неких функций как "friend" важна сама по себе,
        но не исключено, что в этом конкретном случае это и не нужно.
          Это другой вопрос, ЫукпШ. Подозреваю, что знаю, о чём будет следующая тема.
            Qraizer, большое спасибо за такой развёрнутый ответ, не ожидал даже. Прочитал пару раз, вроде понял. Однако написать рабочий код мне это не помогло. При вызове любых шаблонных функций из maon.cpp (как методов, так и свободных функций) ошибка линкера. Почитал в интернете, что нельзя делать определение шаблонных функций в другом файле. Перенёс всё из LazyString.cpp в LazyString.h - таки да, заработало.

            ЫукпШ, нет, не точно. Я действительно не знаю альтернатив.
            Сообщение отредактировано: k.sovailo -
              Цитата k.sovailo @
              Почитал в интернете, что нельзя делать определение шаблонных функций в другом файле.
              Вот. Именно эту проблему я и ванговал. На самом деле можно, но правильно это оформить в коде непросто, проще все определения шаблонов вынести в заголовки.
              0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
              0 пользователей:


              Рейтинг@Mail.ru
              [ Script execution time: 0,0402 ]   [ 17 queries used ]   [ Generated: 28.03.24, 19:34 GMT ]