Предоставянето на swap() за C++ шаблонен клас нарушава std::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 с матрични аргументи (все още). Дефинирах собствения си swap() за Matrix, но когато се опитвам да разменя ints другаде, компилаторът не иска да използва 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: Няма ли да свърши работа дефинирането на swap void swap (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:

Matrix.h:

#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(), грешките изчезват. Все още не съм сигурен защо компилаторът би се опитал да приложи размяната на матрицата към обикновени int, но без него, особено след като преди това работеше добре. - 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 се използва (опитва се да бъде) за размяна на int, но мисля, че сте на нещо, защото размяната на кода ints се намират вътре в my_space, така че това вероятно е причината първо да се постави по подразбиране my_space::swap и следователно необходимостта от изрично използване на std::swap. - person neuviemeporte; 20.07.2012
comment
@Mark : Не, този пример определено извиква std::swap за ints и my_space::swap за Matrix<>s -- ако поставя int foo; в горната част на my_space::swap, този код генерира две предупреждения за непрепратени локални променливи (по едно за всяко инстанциране на Matrix<>). - person ildjarn; 20.07.2012
comment
Този отговор не обяснява защо търсенето намира функцията за приятел (подсказка: не трябва да се намира, според мен, тъй като декларацията за приятел е достъпна само чрез ADL) - person David Rodríguez - dribeas; 20.07.2012
comment
@David : Не знам какъв е проблемът на OP, предвид липсата на реален код във въпроса, така че не знам как да обясня въпросния проблем. - 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-директива е напълно различна от using-declaration. По-специално, първото (using namespace X;) променя търсенето, така че идентификаторите в пространството от имена X да са достъпни на ниво пространство от имена в едно от обграждащите пространства от имена на текущата част от кода (ако изградите дърво от пространства от имена, декларациите биха били налични там, където клонът, съдържащ X, отговаря на клона, съдържащ кода, където се използва using-директивата).

От друга страна, using-declaration (using std::swap;) осигурява using-declaration на функцията std::swap в контекста, където присъства using-declaration . Ето защо трябва да използвате using-declarations, а не using-directives, за да приложите вашите swap функции:

Тест 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-директива [1], символите от пространството от имена ::std щяха да бъдат достъпни за търсения, извършени във функцията ::X::swap(X::A&,X::A&), сякаш бяха декларирани в :: (което е общият предшественик на ::std и ::X). Сега суапът в [3] няма да намери никаква swap функция чрез ADL, така че ще започне да търси swap функции в обхващащото пространство от имена. Първото обхващащо пространство от имена е X и то съдържа функция swap, така че търсенето ще спре и ще се активира разрешаването на претоварване, но X::swap не е валидно претоварване за swap(int&,int&), така че няма да успее да се компилира.

Чрез използване на using-declaration ние привеждаме 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