2013-08-26 3 views
9

Минимальный пример:Функция с ++ друга скрыта функцией класса?

class A 
{ 
    friend void swap(A& first, A& second) {} 
    void swap(A& other) {} 
    void call_swap(A& other) 
    { 
     swap(*this, other); 
    } 
}; 

int main() { return 0; } 

г ++ 4,7 говорит:

friend.cpp: In member function ‘void A::call_swap(A&)’: 
friend.cpp:7:20: error: no matching function for call to ‘A::swap(A&, A&)’ 
friend.cpp:7:20: note: candidate is: 
friend.cpp:4:7: note: void A::swap(A&) 
friend.cpp:4:7: note: candidate expects 1 argument, 2 provided 

Outcomment линия 4:

// void swap(A& other) {} 

... и она отлично работает. Почему и как это исправить, если я хочу сохранить оба варианта моей функции свопинга?

+0

Это похоже на ошибку компилятора? – McKay

+0

@McKay: бывает. Тем не менее, clang дает мне ту же ошибку (с различным объяснением). – Johannes

+0

Как определить функцию вне класса в глобальном пространстве имен? –

ответ

7

Я считаю, что это потому, что компилятор пытается найти функцию внутри класса. Это должно быть минималистское изменение, чтобы заставить его работать (он работает в Visual Studio 2012):

class A; // this and the next line are not needed in VS2012, but 
void swap(A& first, A& second); // will make the code compile in g++ and clang++ 

class A 
{ 
    friend void swap(A& first, A& second) {} 
    void swap(A& other) {} 
    void call_swap(A& other) 
    { 
     ::swap(*this, other); // note the scope operator 
    } 
}; 

int main() { return 0; } 
+0

Нет, это не должно работать. [Clang ++ не принимает его] (http://coliru.stacked-crooked.com/view?id=880bde810cebedd18bdf96d819a0b89c-25dabfc2c190f5ef027f31d968947336), и в стандарте говорится: «Имя, предваряемое унарным оператором области» :: ', проверяется в глобальном масштабе, в блоке перевода, где он используется. ** Имя должно быть объявлено в глобальной области пространства имен ** или должно быть именем, декларация которого видна в глобальной области видимости из-за * using-directive * "в [basic .lookup.qual]/4. Хотя имя функции friend находится в глобальной области действия, оно не было объявлено там. – dyp

+0

Он компилируется в Visual Studio 2012. Я поставил оператор печати в определении друга и запустил его, чтобы подтвердить, что функция friend действительно выполняется, и это произошло. – user1952500

+1

Я не сомневаюсь, что он работает в VS2012. Я сомневаюсь, что это * должно * работать, что означает, что это либо ошибка в компиляторе VC++, либо, по крайней мере, разница в интерпретации стандарта. OP использует g ++, который [не принимает его]] (http://coliru.stacked-crooked.com/view?id=880bde810cebedd18bdf96d819a0b89c-f674c1a6d04c632b71a62362c0ccfc51). – dyp

7

Почему

Внутри класса, имена в область видимости класса скрыть те, в окружающем пространстве имен; так что друг (чье имя находится в пространстве имен, но не доступно непосредственно там) скрывается членом (область в классе) и недоступна в качестве потенциальной перегрузки здесь. (Обновление: или, может быть, это немного сложнее, чем это указано в комментариях. Правила поиска области и имени немного сложно отслеживать, особенно когда речь идет о друзьях).

Как исправить это, если я хочу сохранить оба варианта моей функции подкачки?

Нет идеального решения. Если функции выполняют одно и то же, просто используйте член из других функций-членов. Если вы заявляете другу за пределами определение класса, тогда оно доступно как ::swap; но это немного хрупко, если вы поместите класс в другое пространство имен. (Обновление: или используйте статическую функцию-член, как предложено this answer; я об этом не думал).

+4

Я не думаю, что аргумент * scope * применим здесь: [class.friend]/7 "Определенная функция друга в классе находится в (лексическом) объеме класса, в котором он определен. ". Тем не менее, член класса, найденный с использованием неквалифицированного поиска, скрывает любое имя, найденное с использованием ADL в соответствии с [basic.lookup.argdep]/3 – dyp

+0

@DyP: Да, вы, вероятно, правы. Я не буду пытаться сделать свой ответ полностью точным, так как мой мозг, вероятно, расплавится, если я попробую. –

+2

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

7

В качестве обходного решения вы можете объявить static версию swap. Затем вы можете объявить версию friend для вызова версии static.

class A 
{ 
public: 
    friend void swap(A& first, A& second) { A::swap(first, second); } 
private: 
    static void swap(A& first, A& second) {} 
    void swap(A& other) {} 
    void call_swap(A& other) 
    { 
     swap(*this, other); 
    } 
}; 

int main() { 
    A a, b; 
    swap(a, b); 
} 
0

Вы можете также использовать вспомогательную функцию, так как в этом случае

template<class T> 
void swap_elems(T& lhs, T& rhs) 
{ 
    using namespace std; 
    swap(lhs, rhs); 
} 

class A 
{ 
friend void swap(A& first, A& second) { first.swap(second); } 

    public: 
    void swap(A& other) {} 
    void call_swap(A& other) 
    { 
     swap_elems(*this, other); 
    } 
}; 
3

Придерживайтесь стандартной замены идиомы, и вы не будете иметь проблемы:

void call_swap(A& other) { 
    using std::swap; 
    swap(*this, other); 
} 

Или используйте обертку Boost.Swap:

void call_swap(A& other) { 
    boost::swap(*this, other); 
} 

Это в значительной степени эквивалентно решению @ Хуана, за исключением того, что вы сами не пишете помощника.

+0

Но это тоже не найдет функцию друга. –

+0

Да, будет. Он найдет его с ADL. –

+0

@SebastianRedl Согласен, хотя для более сложных классов это не сработает. – Johannes

0

Что вы наблюдаете здесь является то, что при отсутствии предыдущей декларации в friend функции, friend судна в классе впрыскивает имя в вмещающем пространство имен, но НЕ в области видимости класса. Единственное, что происходит в классе, это то, что назначенной функции предоставляется доступ к закрытым атрибутам.

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

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