Looking at the source code of boost::polygon, I have seen many applications of the following theme:
#include <iostream>
namespace B {
struct A {
void foo() const { std::cout << "foo" << std::endl; }
};
void bar(const A &a) { a.foo(); }
void baz() { std::cout << "baz" << std::endl; }
}
int main(int argc, char **argv) {
B::A a;
bar(a);
B::baz(); // simply calling baz() does not work
return 0;
}
How is it that bar(a) can be called without extra qualifications? I would have expected that only B::bar(a) would compile.
When the function does not have an argument inside the namespace, this does not occur.
According to ISO C++14 standard, at §3.4.2:
When the postfix-expression in a function call is an unqualified-id, other namespaces not considered during the usual unqualified lookup may be searched, and in those namespaces, namespace-scope friend function or function template declarations not otherwise visible may be found. These modifications to the search depend on the types of the arguments (and for template template arguments, the namespace of the template argument).
And following:
For each argument type T in the function call, there is a set of zero or more associated namespaces and a set of zero or more associated classes to be considered. The sets of namespaces and classes is determined entirely by the types of the function arguments..
If T is a class type (including unions), its associated classes are: the class itself; the class of which it is a member, if any; and its direct and indirect base classes. Its associated namespaces are the innermost enclosing namespaces of its associated classes.
Actually you can even prevent this from happening by enclosing the function name:
(bar)(a); // doens't compile
while
(B::bar)(a); // does compile
Mind also that this applies only to the innermost namespace, which means that in the following situation you need to qualify the namespace:
namespace B {
namespace C {
struct A {};
}
void bar(const C::A& a) { ... }
}
Related
[namespace.memdef]/3 (emphases are mine):
If a friend declaration in a non-local class first declares a class,
function, class template or function template106 the friend is a member
of the innermost enclosing namespace. The friend declaration does not
by itself make the name visible to unqualified lookup (6.4.1) or
qualified lookup (6.4.3). [ Note: The name of the friend will be
visible in its namespace if a matching declaration is provided at
namespace scope (either before or after the class definition granting
friendship). —end note ] If a friend function or function template is
called, its name may be found by the name lookup that considers
functions from namespaces and classes associated with the types of the
function arguments (6.4.2). If the name in a friend declaration is
neither qualified nor a template-id and the declaration is a function
or an elaborated-type-specifier, the lookup to determine whether the
entity has been previously declared shall not consider any scopes
outside the innermost enclosing namespace. [ Note: The other forms of
friend declarations cannot declare a new member of the innermost
enclosing namespace and thus follow the usual lookup rules. —end note
] [ Example: ...
The code below executes correctly irrespective of the fact whether the friend declaration is or is not the first declaration in its namespace.
#include<iostream>
namespace N{
struct A;
void f(A&); // If you comment out this declaration, i.e., if the
// friend declaration turns out to be the first
// declaration in namespace N, the code will still
// execute correctly, i.e., printing the same result
// below.
struct A {
friend void f(A& ra) { std::cout << "friend void f(A&)\n" << ra.i; }
private:
int i = 100;
};
}
N::A a;
int main(){
f(a);
}
This snippet prints out the following:
friend void f(A&)
100
It is found with ADL because you pass a single parameter from namespace N. Example can be changed to this:
namespace N{
void f(int); // If you comment out this declaration, i.e., if the
// friend declaration turns out to be the first
// declaration in namespace N, the code will fail to compile.
struct A {
friend void f(int) { std::cout << "friend void f()\n"; }
private:
int i = 100;
};
}
int main(){
N::f(1);
}
online compiler
Consider the following code:
#include <iostream>
struct foo {
friend void bar(foo) {}
void foobar() {
std::cout << &bar << '\n'; // error
}
};
int main() {
bar(foo{}); // ok, visible through ADL
foo{}.foobar();
}
gcc gives me this error:
main.cpp: In member function 'void foo::foobar()':
main.cpp:7:23: error: 'bar' was not declared in this scope
std::cout << &bar << '\n'; // error
^~~
That's because bar is a friend function defined in the class itself, making it invisible in the global namespace. The only way to access it is through ADL, but I have not found a way to use ADL to get the address of bar.
So my question is, how can I take the address of bar? Is there any other way than to define bar outside of foo?
You could declare the friend function in the enclosing namespace scope in a different translation unit:
In foo.hpp:
#include <iostream>
struct foo;
void (*get_bar_address())(foo);
struct foo {
friend void bar(foo) {}
void foobar() {
std::cout << get_bar_address() << '\n'; // no error
}
};
In foo.cpp
#include "foo.hpp"
void bar(foo);
void (*get_bar_address())(foo){
return bar;
}
It seems to be impossible to get exactly what you want. The problem is that an inline friend declared function will only be found by argument-dependant lookup, as defined in the C++ standard 11 and 14 in
7.3.1.2
3 Every name first declared in a namespace is a member of that namespace. If a friend declaration in a non-local class first declares a class or function the friend class or function is a member of the innermost enclosing namespace. The name of the friend is not found by unqualified lookup (3.4.1) or by qualified lookup (3.4.3) until a matching declaration is provided in that namespace scope (either before or after the class definition granting friendship). If a friend function is called, its name may be found by the name lookup that considers functions from namespaces and classes associated with the types of the function arguments (3.4.2). If the name in a friend declaration is neither qualified nor a template-id and the declaration is a function or an elaborated-type-specifier, the lookup to determine whether the entity has been previously declared shall not consider any scopes outside the innermost enclosing namespace.
That means it is only ever be found by argument dependent lookup. Revising the rules, argument dependent lookup is only applied within a function call expression. Hence you will not be able to retrieve the address of the function as any such an expression will evaluate to the result of the call.
In case you only need a pointer referring to a function with the same functionality as bar, you could however utilize lambdas to get a pointer to such a function nevertheless.
void foo::foobar() {
using fntype = void(*)(foo);
std::cout << (fntype)[](foo f){ bar(f); } << '\n'; // error
}
The downside is that any other lambda will likely result in an intirely different address. You could however offer the address in a different (static) member function if the uniqueness is of importance.
Did you try something like this? I find it interesting that you would declare a friend function from inside the scope of the struct/class but hey.
#include <iostream>
// forward declare foo so you can forward declare bar :)
struct foo;
// forward declare bar so an address is available on the line that references it.
void bar(foo);
struct foo
{
friend void bar(foo)
{
std::cout << "bar called" << std::endl;
}
void foobar()
{
std::cout << "address of bar " << &bar << '\n'; // error
}
};
int main()
{
bar(foo{}); // ok, visible through ADL
foo{}.foobar();
}
Tried it and it works LOL not sure why:
$ ./test.exe
bar called
address of bar 1
though address of 1 is suspicious. :) but hey it compiled and did something. maybe you can take it further or someone can explain why this compiled/worked in the first place.
Consider the following:
namespace N {
struct A { };
struct B {
B() { }
B(A const&) { }
friend void f(B const& ) { }
};
}
int main() {
f(N::B{}); // ok
f(N::A{}); // error
}
In the first case, the case succeeds - we consider the associated namespaces of N::B and find N::f(B const&). Great.
The second case fails. Why? According to [namespace.memdef]:
If a friend declaration in a non-local class first declares a class, function, class template or function template the friend is a member of the innermost enclosing namespace. [...] If a friend function or function template
is called, its name may be found by the name lookup that considers functions from namespaces and classes associated with the types of the function arguments (3.4.2).
The associated namespace of N::A is N, of which f is a member, so why is it not found by lookup?
It's because f is not declared in an associated class. B is an associated class when the argument is of type B, but not when the argument is of type A.
I quote from [basic.lookup.argdep]/4, emphasis mine:
When considering an associated namespace, the lookup is the same as the lookup performed when the
associated namespace is used as a qualifier (3.4.3.2) except that:
— Any using-directives in the associated namespace are ignored.
— Any namespace-scope friend functions or friend function templates declared in associated classes are
visible within their respective namespaces even if they are not visible during an ordinary lookup (11.3).
— All names except those of (possibly overloaded) functions and function templates are ignored.
Following codes are in two source files.
First:
namespace A {
// two friends; neither is declared apart from a friend declaration
// these functions implicitly are members of namespace A
class C {
friend void f2(); // won’t be found, unless otherwise declared
friend void f1(const C&); // found by argument-dependent lookup
};
}
int main()
{
A::C obj;
f1(obj); // ok: find A::f through the friend declaration in A::C
A::f2(); // no member named f2 in namespace A
}
and the second:
#include <iostream>
namespace A {
class C;
void f1(const C&) {
std::cout << 1;
}
void f2() {
std::cout << 2;
}
}
The first piece of code is copied from C++ primer, the only difference is C++ primer call f2() without prefix the namespace. The second piece is my complement. I wanna know now that f1 and f2 implicitly are members of namespace A, why A::f2() is still wrong while f1(obj) can be found by ADL?
This is the rule, found in 7.3.1.2, that causes A::f2() to fail:
If a friend declaration in a non-local class first declares a class, function, class template or function template the friend is a member of the innermost enclosing namespace. The friend declaration does not by itself make the name visible to unqualified lookup (3.4.1) or qualified lookup (3.4.3).
In the first block of code, f2 is in the lexical scope of A::C, i.e. the name is visible within the scope of A::C. It is not visible outside A::C.
To make f2 visible outside the scope of A::C, you'll need to declare or define it in A.
void foo()
{
bar(); // error: ‘bar’ has not been declared
}
void bar()
{
}
namespace N
{
void foo()
{
N::bar(); // error: ‘bar’ is not a member of ‘N’
}
void bar()
{
}
}
class C
{
static void foo()
{
C::bar(); // works just fine
}
static void bar()
{
}
};
What is the rationale behind this inconsistency of treating calls to functions above their declaration? How come I can do it inside a class, but not inside a namespace or at global scope?
You can define member functions either inside the class, or after the class declaration, or some of each.
To get some consistency here, the rules for a class with functions defined inline is that it still has to be compiled as if the functions were defined after the class.
Your code
class C {
static void foo()
{
C::bar(); // works just fine
}
static void bar()
{ }
};
compiles the same as
class C {
static void foo();
static void bar();
};
void C::foo()
{ C::bar(); }
void C::bar()
{ }
and now there is no magic in the visibility, because the functions can all see everything declared in the class.
Namespaces can be reopened and new things can be added in any place.
Classes cannot be reopened - all their contents must be put in
single place.
Function prototypes are legal in namespaces but not in classes.
You can write
namespace n
{
void foo();
void bar()
{
foo();
}
void foo()
{
}
}
But not
class C
{
void foo();
void bar()
{
foo();
}
void foo()
{
}
}
So classes need such feature much more and it is much easier to implement it for them than for namespaces.
Well maybe because you have your class declaration in one place, and the compiler can easily obtain the information of it's members.
A namespace, on the other hand, can have it's stuff in tons of different files, and you can't expect the compiler to look through them, because it doesn't know where to look in the first place.
To avoid this, just use function prototypes.
I'm not sure but my thought is that a class is somewhat an object (badly used) where all its internal components work together (generally speaking), its member will definitely need its methods.
But a namespace is different, functions are not related. It means that a function isnt intended to work with every other functions inside the namespace.
So splitting declaration and definitions is the best thing you could do.
If foo() needs bar() its most likely going to be in the same declaration file, and would work that way
See the quote below from the Standard
3.3.7 Class scope [basic.scope.class]
1) The following rules describe the scope of names declared in classes. 1) The potential
scope of a name declared in a class consists not only of the
declarative region following the name’s point of declaration, but also
of all function bodies, default arguments, and
brace-or-equalinitializers of non-static data members in that class
(including such things in nested classes).
2) A name N used in a class S shall refer to the same declaration in its context and when re-evaluated in
the completed scope of S. No diagnostic is required for a violation of this rule.
typedef int c;
enum { i = 1 };
class X {
char v[i]; // error: i refers to ::i
// but when reevaluated is X::i
int f() { return sizeof(c); } // OK: X::c
char c;
enum { i = 2 };
};