Предоставление swap() для класса шаблона C++ нарушает работу std::swap()?

Я пытался реализовать идиому copy-and-swap в своем пользовательский класс Matrix, и у меня возникли проблемы с реализацией swap() так, как это было предложено в вопросе, связанном с:

(Я использовал компилятор из MS VS2010 IDE, диалект — старый добрый C++03.)

// matrix.h

namespace my_space 
{

template<typename T> class Matrix
{
public:
    /* ... */

    friend void swap(Matrix<T> &first, Matrix<T> &second)
    {
        using std::swap;
        swap(first.width_, second.width_);
        swap(first.height_, second.height_);
        swap(first.data_, second.data_);
    }
};

} // namespace

Теперь у меня проблемы с доступом к обычному std::swap() в коде функций, находящихся в этом пространстве имен:

// some_code.cpp:
#include "matrix.h"
#include <algorithm>

using namespace my_space;
using namespace std;

// SomeClass is part of my_space!
void SomeClass::some_function()
{
    int a = 3, b = 7;
    swap(a,b);  // I wan't std::swap!
}

К сожалению, по какой-то причине my_space::swap() для Matrix кажется псевдонимом всех других вызовов std::swap(), и я понятия не имею, почему, поскольку аргументы не подходят, и ADL должен отдавать предпочтение std::swap:

1>f:\src\some_code.cpp(653): error C3767: 'swap': candidate function(s) not accessible
1>          could be the friend function at 'f:\src\matrix.h(63)' : 'swap'  [may be found via argument-dependent lookup]

(Ошибка повторяется 10 раз для каждой строки, где я пытаюсь использовать std::swap)

Всегда ли my_space::swap() имеет приоритет перед std::swap() в my_space, даже если аргументы не подходят? Дело не в том, что std::swap() не видно, и до создания my_space::swap() он работал нормально.


person neuviemeporte    schedule 19.07.2012    source источник
comment
Где вы вызываете swap с Matrix<> аргументами? В вашем примере используются ints... Ошибка выглядит так, как будто вы пытаетесь поменять местами два разных типа объектов Matrix<> (т.е. с двумя разными Ts в Matrix<T>) — почему это должно работать?   -  person ildjarn    schedule 20.07.2012
comment
@ildjarn: я не вызываю swap с аргументами Matrix (пока). Я определил свой собственный swap() для Matrix, но когда я пытаюсь поменять местами целые числа в другом месте, компилятор не хочет использовать std::swap и по какой-то причине возвращается к моему Matrix swap().   -  person neuviemeporte    schedule 20.07.2012


Ответы (4)


Подход, используемый контейнерами STL, использует функцию-член, а затем перегружает статическую функцию. Например:

template<class T, class Alloc=std::allocator<T> >
class vector
{
   T *data;
   size_t n;
   size_t max_n;
public:
   void swap(vector<T, Alloc> &other)
   {
      swap(this->data, other.data);
      swap(this->n, other.n);
      swap(this->max_n, other.max_n);
   }
};

template<class T, class A>
void swap(vector<T, A> &lhs, vector<T, A> &rhs)
{
   lhs.swap(rhs);
}

В предлагаемом классе Matrix просто используйте тот же подход...

namespace my_space
{
template<typename T>
class Matrix
{
   unsigned width_;
   unsigned height_;
   std::vector<T> data_;
public:
   void swap(Matrix<T> &other)
   {
      std::swap(this->width_, other.width_);
      std::swap(this->height_, other.height_);
      std::swap(this->data_, other.data_);  // calls this->data_.swap(other.data_);
   }
};
}

namespace std
{
   template<typename T>
   void swap(my_space::Matrix<T> &lhs, my_space::Matrix<T> &rhs)
   {
      lhs.swap(rhs);
   }
}
person Andrew    schedule 20.07.2012
comment
Спасибо! Я не думал явно помещать свой swap() в пространство имен std. Я принял этот подход, и все работает нормально без каких-либо конфликтов. - person neuviemeporte; 20.07.2012
comment
@neuviemeporte: Хотя это законно, это, безусловно, менее чем идиоматично. - person ildjarn; 22.07.2012
comment
статическая функция глобальная функция? - person curiousguy; 26.07.2012
comment
@curiousguy: Ты прав. Технически это не статическая функция, а не перегруженная функция шаблона. - person Andrew; 28.07.2012

Включите следующую строку в Matrix:

template<typename U> friend void swap(Matrix<U> &first, Matrix<U> &second);

и определите swap вне класса. Причина, по которой вы получаете ошибку function template has already been defined, заключается в том, что каждый экземпляр Matrix<unsigned short> и Matrix<char> будет содержать одно и то же определение вашей функции подкачки, так как вы определили функцию друга внутри шаблона Matrix.

person Jesse Good    schedule 19.07.2012
comment
Я не определял его где-либо еще и хотел, чтобы он был внутри Matrix для ясности и непосредственной видимости. В любом случае должен быть внутри заголовка в качестве шаблона. В любом случае, сохранение только объявления в теле класса и перемещение определения наружу помогли, код компилируется, хотя не уверен, почему... - person neuviemeporte; 20.07.2012
comment
@neuviemeporte: поскольку вы определяете функцию друга внутри шаблона Matrix, каждый экземпляр функции подкачки будет иметь отдельное определение. Вот почему вам нужно определить его вне класса Matrix. - person Jesse Good; 20.07.2012
comment
Все еще запутанный, swap() является шаблонной функцией, поэтому swap(Matrix‹unsigned short›, Matrix‹unsigned short›) — это функция, отличная от функции swap(Matrix‹char›, Matrix‹char›) — почему тогда Matrix‹char› и Matrix‹unsigned short› содержат одно и то же определение? Несмотря на это, определение дружественной функции внутри тела класса эквивалентно определению глобальной функции, поэтому в любом случае она не должна быть частью какой-либо матрицы...? - person neuviemeporte; 20.07.2012
comment
@neuviemeporte, swap - это функция шаблона, но она никак не зависит от параметра шаблона класса. Matrix<int> и Matrix<double> будут определять идентичные функции swap. - person Mark Ransom; 20.07.2012
comment
@MarkRansom: Спасибо! Я запутался и не подумал о двух вариантах Matrix, загрязняющих глобальное пространство двумя идентичными копиями swap()... что заставляет меня снова задуматься - а должен ли swap быть шаблонной функцией? - person neuviemeporte; 20.07.2012
comment
@neuviemeporte, да, это должна быть функция шаблона, но вне класса. Я не знаю, почему это не считается специализацией std::swap, вероятно, из-за разных пространств имен. - person Mark Ransom; 20.07.2012
comment
@MarkRansom: Не поможет ли определение друга недействительным обменом (Matrix‹T›, Matrix‹T›) внутри класса? Таким образом, swap() зависит от исходного типа, и каждый вариант Matrix создаст свой собственный глобальный обмен. Или я что-то упускаю? - person neuviemeporte; 20.07.2012
comment
@neuviemeporte, наличие его внутри класса означает, что он является членом, и вам нужно будет вызывать его из объекта типа Matrix, который он даже не собирается использовать. Даже если вы объявите его static, ему все равно понадобится квалификатор имени класса. - person Mark Ransom; 20.07.2012
comment
@MarkRansom: функция друга не является членом, даже если она определена внутри класса, и ее можно вызывать без экземпляра этого класса. Это просто забавный способ определить встроенную глобальную функцию. См. код для swap() в ответе ildjarn. - person neuviemeporte; 20.07.2012
comment
@MarkRansom: я так не думаю, с чего бы это? Matrix внутри шаблона является сокращением для Matrix<T>, что означает, что каждый экземпляр будет давать другой обмен (другой тип аргумента). - person David Rodríguez - dribeas; 20.07.2012
comment
@DavidRodríguez-dribeas: вопрос редактировался несколько раз. Первоначально swap был шаблоном функции для Matrix<U>, а не Matrix<T>, который давал одно и то же определение для каждого экземпляра. - person Jesse Good; 20.07.2012
comment
Приносим извинения всем участникам за путаницу и множественные правки. Я пробовал разные вещи по мере того, как мое понимание проблемы развивалось. Я глубоко благодарен за понимание, несмотря на неблагоприятные обстоятельства, особенно Марку, Джесси и Дэвиду. Хотел бы я +1 вам, ребята, несколько раз. - person neuviemeporte; 20.07.2012

Следующие сборки чисто для меня с VC++ 2010 SP1:

Матрица.ч:

#pragma once

#include <algorithm>
#include <vector>

namespace my_space
{
    template<typename T>
    class Matrix
    {
    public:
        Matrix(unsigned const w, unsigned const h)
          : width_(w), height_(h), data_(w * h)
        { }

    private:
        unsigned width_;
        unsigned height_;
        std::vector<T> data_;

        friend void swap(Matrix& lhs, Matrix& rhs)
        {
            using std::swap;
            swap(lhs.width_,  rhs.width_);
            swap(lhs.height_, rhs.height_);
            swap(lhs.data_,   rhs.data_);
        }
    };
}

.cpp:

#include "Matrix.h"

int main()
{
    using namespace my_space;
    using std::swap;

    int a(0), b(1);
    swap(a, b);

    Matrix<int> c(2, 3), d(4, 5);
    swap(c, d);

    Matrix<short> e(6, 7), f(8, 9);
    swap(e, f);
}

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

person ildjarn    schedule 20.07.2012
comment
Намек принят, извините, я немного переборщил с первым S, боюсь, вопрос слишком длинный. :( - person neuviemeporte; 20.07.2012
comment
Вы использовали только один вариант Matrix. Попробуйте также использовать Matrix<char> в той же функции main. - person Mark Ransom; 20.07.2012
comment
@ildjarn: Спасибо за беспокойство; важным моментом является использование std::swap в main(). Если я сделаю это в своих TU, требующих std::swap(), ошибки исчезнут. Все еще не уверен, почему компилятор попытается применить своп Matrix к простым целым числам, хотя и без него, тем более что раньше он работал нормально. - person neuviemeporte; 20.07.2012
comment
@neuviemeporte, я думаю, если вы поменяете местами две директивы using namespace в своем коде, это может начать работать. Я подозреваю, что в этом примере используется std::swap вместо my_space::swap при замене матриц. - person Mark Ransom; 20.07.2012
comment
@MarkRansom: изменение порядка использования пространства имен не помогло, и проблема в другом: my_space::swap используется (пытается) для замены целых чисел, но я думаю, что вы что-то напутали, потому что замена кода целые числа находятся внутри my_space, поэтому, вероятно, это причина того, что сначала используется my_space::swap по умолчанию, и, следовательно, необходимость явного использования std::swap. - person neuviemeporte; 20.07.2012
comment
@Mark: Нет, этот пример определенно вызывает std::swap для int и my_space::swap для Matrix<> - если я помещу int foo; в начало my_space::swap, этот код генерирует два предупреждения о локальных переменных, на которые нет ссылок (по одному для каждого экземпляра Matrix<>). - person ildjarn; 20.07.2012
comment
Этот ответ не объясняет, почему поиск находит функцию друга (подсказка: я считаю, что ее не следует находить, поскольку объявление друга доступно только через ADL) - person David Rodríguez - dribeas; 20.07.2012
comment
@David Дэвид: я не знаю, в чем проблема ОП, учитывая отсутствие реального кода в вопросе, поэтому я не знаю, как объяснить указанную проблему. - person ildjarn; 20.07.2012

Если код действительно похож на то, что вы опубликовали, это проблема с компилятором. Код отлично компилируется в clang++, как и должно быть.

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


Тест 1 (функция недоступна на уровне пространства имен без явного объявления):

namespace A {
   struct B {
      friend void f();  // [1]
   };
   // void f();         // [2]
}
void A::f() {}          // [3]

В [1] мы добавляем объявление друга, которое объявляет void A::f() другом A::B. Без дополнительного объявления в [2] на уровне пространства имен определение в [3] не скомпилируется, поскольку, находясь за пределами пространства имен A, это определение также не является самообъявлением.


Смысл здесь в том, что, поскольку функция недоступна для поиска на уровне пространства имен, а только через ADL на Matrix<T> (для некоторого конкретного инстанцирующего типа T), компилятор не может найти это как совпадение с перестановкой двух значений int.

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

Дружественная функция, определенная внутри класса, объявит и определит функцию уровня пространства имен, и опять же, объявление будет доступно только внутри класса и доступно через ADL. Когда это делается внутри шаблона, он определяет свободную функцию без шаблона на уровне пространства имен для каждого экземпляра шаблона, использующего эту функцию. То есть он будет генерировать другие определения. Обратите внимание, что внутри области шаблона класса имя шаблона идентифицирует конкретизируемую специализацию, то есть внутри Matrix<T> идентификатор Matrix обозначает не шаблон, а одну конкретизацию шаблона.

Тест 2


namespace X {
   template <typename T>
   struct A {
      friend void function( A ) {}
   };
   template <typename T>
   void funcTion( A<T> ) {}
}
int main() {
   using namespace X;
   A<int> ai;    function(ai); funcTion(ai);
   A<double> ad; function(ad); funcTion(ad);
}

$ make test.cpp
$ nm test | grep func | c++filt
0000000100000e90 T void X::funcTion<double>(A<double>)
0000000100000e80 T void X::funcTion<int>(A<int>)
0000000100000e70 T X::function(A<double>)
0000000100000e60 T X::function(A<int>)

Результатом nm является список символов, а c++filt преобразует искаженные имена в эквиваленты в синтаксисе C++. Вывод программы ясно показывает, что X::funcTion является шаблоном, созданным для двух типов, а X::function – двумя перегруженными функциями без шаблонов. Опять же: две функции без шаблонов.


Заявление о том, что он сгенерирует такую ​​же функцию, не имеет особого смысла, учитывая, что у него был вызов функции, скажем, std::cout << lhs, код должен выбрать правильную перегрузку operator<< для текущего экземпляра функции. Не существует единого operator<<, который может принимать, скажем, int и unsigned long или std::vector<double> (Ничто не мешает вам создать экземпляр шаблона с любым типом.

Ответ ildjarn предлагает альтернативу, но не дает объяснения поведения. Альтернатива работает, поскольку директива-использования полностью отличается от объявления-использования. В частности, первый (using namespace X;) модифицирует поиск, чтобы идентификаторы в пространстве имен X были доступны на уровне пространства имен в одном из включающих пространств имен текущего фрагмента кода (если вы строите дерево пространств имен, объявления будут доступны там, где ветвь, содержащая X, встречается с ветвью, содержащей код, в котором используется using-directive).

С другой стороны, декларация использования (using std::swap;) предоставляет декларацию функции std::swap в контексте, где присутствует декларация использования. . Вот почему вы должны использовать using-declarations, а не using-directives для реализации ваших функций подкачки:

Тест 3


namespace Y { struct Z {}; void swap( Z&,Z& ); }
namespace X {
   struct A { int a; Y::Z b; };
   void swap( A& lhs, A& rhs ) {
      //using namespace std;       // [1]
      using std::swap;             // [2]
      swap( lhs.a, rhs.a );        // [3]
      swap( lhs.b, rhs.b );        // [4]
   }
}

Если бы мы использовали using-directive [1], символы из пространства имен ::std были бы доступны для поиска, выполняемого внутри функции ::X::swap(X::A&,X::A&), как если бы они были объявлены в :: (который является общим предком ::std и ::X). Теперь подкачка в [3] не найдет ни одной swap функции через ADL, поэтому она начнет поиск swap функций в окружающем пространстве имен. Первое окружающее пространство имен — X, и оно содержит функцию swap, поэтому поиск остановится и включится разрешение перегрузки, но X::swap не является допустимой перегрузкой для swap(int&,int&), поэтому оно не сможет скомпилироваться.

Используя объявление-использования, мы помещаем объявление std::swap в область видимости X::swap (внутри функции!). Опять же, ADL не будет применяться в [3], и начнется поиск. В текущей области (внутри функции) он найдет объявление std::swap и создаст экземпляр шаблона. В [4] срабатывает ADL, и он ищет функцию swap, определенную внутри функции ::Y::Z и/или в ::Y, и добавляет ее к набору перегрузок, найденных в текущей области ( снова ::std::swap). На данный момент ::Z::swap соответствует лучше, чем std::swap (при идеальном совпадении нешаблонная функция лучше подходит, чем шаблонная), и вы получаете ожидаемое поведение.


person David Rodríguez - dribeas    schedule 20.07.2012
comment
@JesseGood: Чего вам не хватает, так это того, что аргумент имеет решающее значение. В частности, добавьте простой A в свою программу, и вы заметите, что ошибка исчезла. В приведенном вами примере (который отличается от вопроса) функция не принимает аргументов (в качестве альтернативы, независимых аргументов или зависимых аргументов, которые разрешаются в одни и те же типы для разных экземпляров), и это означает, что все экземпляры шаблона определите ту же функцию. Но в вопросе аргументы содержат текущую реализацию. - person David Rodríguez - dribeas; 20.07.2012
comment
@JesseGood: Также обратите внимание на еще один побочный эффект того, как работают объявления друзей, потому что он будет найден только ADL, а в вашем примере функция не принимает аргументов, зависящих от шаблона, фактически невозможно вызвать функцию. - person David Rodríguez - dribeas; 20.07.2012
comment
Правильно, я согласен с тем, что вы говорите. Однако в исходном вопросе до того, как он был отредактирован (посмотрите историю редактирования), программа была подобной этой, которая так я показываю в своем ответе. - person Jesse Good; 21.07.2012