Improve friend function documentation

Add unqualified friend example in docs as a testcase
This commit is contained in:
William S Fulton 2024-01-14 16:05:48 +00:00
parent 37aeab999a
commit 158fbdc760
12 changed files with 257 additions and 20 deletions

View File

@ -237,6 +237,12 @@
<li><a href="SWIGPlus.html#SWIGPlus_nn15">Protection</a>
<li><a href="SWIGPlus.html#SWIGPlus_nn16">Enums and constants</a>
<li><a href="SWIGPlus.html#SWIGPlus_nn17">Friends</a>
<ul>
<li><a href="SWIGPlus.html#SWIGPlus_friend_classes">Friend classes</a>
<li><a href="SWIGPlus.html#SWIGPlus_friend_function_definitions">Friend function definitions</a>
<li><a href="SWIGPlus.html#SWIGPlus_friend_function_declarations">Friend function declarations</a>
<li><a href="SWIGPlus.html#SWIGPlus_friends_unqualified">Unqualified friend functions</a>
</ul>
<li><a href="SWIGPlus.html#SWIGPlus_nn18">References and pointers</a>
<li><a href="SWIGPlus.html#SWIGPlus_nn19">Pass and return by value</a>
<li><a href="SWIGPlus.html#SWIGPlus_nn20">Inheritance</a>

View File

@ -34,6 +34,12 @@
<li><a href="#SWIGPlus_nn15">Protection</a>
<li><a href="#SWIGPlus_nn16">Enums and constants</a>
<li><a href="#SWIGPlus_nn17">Friends</a>
<ul>
<li><a href="#SWIGPlus_friend_classes">Friend classes</a>
<li><a href="#SWIGPlus_friend_function_definitions">Friend function definitions</a>
<li><a href="#SWIGPlus_friend_function_declarations">Friend function declarations</a>
<li><a href="#SWIGPlus_friends_unqualified">Unqualified friend functions</a>
</ul>
<li><a href="#SWIGPlus_nn18">References and pointers</a>
<li><a href="#SWIGPlus_nn19">Pass and return by value</a>
<li><a href="#SWIGPlus_nn20">Inheritance</a>
@ -1167,64 +1173,218 @@ Members declared as <tt>const</tt> are wrapped as read-only members and do not c
<H2><a name="SWIGPlus_nn17">6.9 Friends</a></H2>
<H3><a name="SWIGPlus_friend_classes">6.9.1 Friend classes</a></H3>
<p>
Friend declarations are recognised by SWIG. For example, if
you have this code:
Friend classes are a C++ feature that do not affect SWIG wrappers.
SWIG simply parses the friend class declarations, but they are effectively ignored
as they have no meaningful effect for wrappers.
An example of friend classes:
</p>
<div class="code">
<pre>
class Foo {
public:
class X;
class Y;
class C {
// Friend classes have no effect on generated wrappers
friend class X;
friend Y;
...
friend void blah(Foo *f);
};
</pre>
</div>
<H3><a name="SWIGPlus_friend_function_definitions">6.9.2 Friend function definitions</a></H3>
<p>
A friend function definition in a C++ class defines a non-member function of the class
and simultaneously makes it a friend of the class.
For example, if you have this code:
</p>
<div class="code">
<pre>
class Buddy {
int val;
friend int blah(Buddy *b) { return b-&gt;val; }
public:
...
};
</pre>
</div>
<p>
then the <tt>friend</tt> declaration does result in a wrapper code
equivalent to one generated for the following declaration
then the <tt>friend</tt> function definition results in wrapper code
equivalent to one generated for the following:
</p>
<div class="code">
<pre>
class Foo {
class Buddy {
int val;
friend int blah(Buddy *b);
public:
...
};
void blah(Foo *f);
int blah(Buddy *b) { return b-&gt;val; }
</pre>
</div>
<p>
A friend declaration, as in C++, is understood to be in the same scope
where the class is declared, hence, you can have
Access from target languages is thus as if <tt>blah</tt> was wrapped as a non-member function.
The function is available and wrapped even if the friend is defined with private or protected access.
</p>
<p>
A friend definition, as in C++, is understood to be in the same scope
that the class is defined in, hence the scoping required for SWIG directives, such as <tt>%ignore</tt>, is as follows:
</p>
<div class="code">
<pre>
%ignore bar::blah(Foo *f);
%ignore bar::blah(Buddy *b);
// Not: %ignore bar::Buddy::blah(Buddy *b);
namespace bar {
class Foo {
class Buddy {
int val;
friend int blah(Buddy *b) { return b-&gt;val; }
public:
...
friend void blah(Foo *f);
...
};
}
</pre>
</div>
<p>
and a wrapper for the method 'blah' will not be generated.
and a wrapper for <tt>blah</tt> will not be generated.
</p>
<H3><a name="SWIGPlus_friend_function_declarations">6.9.3 Friend function declarations</a></H3>
<p>
A C++ class can specify friends via friend function declarations.
These functions are allowed access to the private and protected members of a class.
This is pure C++ functionality and these friend function declarations are hence quietly ignored by SWIG and do not result in any wrappers.
Well, not always! The C++ rules for friends that SWIG needs to follow are not that simple.
Technically, only qualified function declarations are silently ignored by SWIG.
Below are some examples of qualified friend declarations in <tt>A</tt> that are quietly ignored:
</p>
<div class="code">
<pre>
struct B {
int f();
B();
~B();
...
};
int g();
class A {
public:
// The following friend function-declarations are silently ignored (including constructor and destructor friends)
friend B::B();
friend B::~B();
friend int B::f();
friend int ::g();
...
};
</pre>
</div>
<p>
In the example above, if SWIG parses the struct <tt>B</tt> and global function <tt>g()</tt>,
then they are of course wrapped as normal.
</p>
<H3><a name="SWIGPlus_friends_unqualified">6.9.4 Unqualified friend functions</a></H3>
<p>
Further clarification is required regarding both friend function definitions and declarations.
In C++, friend function definitions can only be unqualified, whereas, friend function declarations can be either unqualified or qualified. Qualified friend function declarations are silently ignored by SWIG as covered in the previous section. SWIG does generate wrappers for any unqualified friend functions that it parses. This section goes through some of the complexities of wrapping unqualified friend functions.
</p>
<p>
Consider an unqualified friend function definition:
</p>
<div class="code">
<pre>
class Chum {
int val;
friend int blah() { Chum c; c.private_function(); return c.val; }
void private_function();
public:
...
};
</pre>
</div>
<p>
The <tt>Chum::blah()</tt> friend is very similar to the <tt>Buddy::blah(Buddy *)</tt> friend presented earlier.
However, the generated code to call <tt>blah()</tt> may not compile unlike the code to call <tt>blah(Buddy *)</tt>.
The compiler error will be something like:
<div class="shell">
<pre>
error: 'blah' was not declared in this scope
</pre>
</div>
<p>
The reason one works and the other doesn't is due to the rules around unqualified friend definitions/declarations. Broadly, friends are not visible for lookup except via argument dependent lookup that considers the class that the friend is defined/declared in, unless there is a <i>matching declaration</i> at namespace scope.
This will probably only make sense if you are conversant with this C++ concept, which is covered quite well at <a href="https://en.cppreference.com/w/cpp/language/adl">Argument-dependent lookup</a>.
In our examples, <tt>blah(Buddy *)</tt> is visible via argument dependent lookup, but <tt>blah()</tt> is not. The solution is thus to provide a <i>matching declaration</i> in order to make the function visible to the compiler. Simply add:
</p>
<div class="code">
<pre>
int blah();
</pre>
</div>
<p>
SWIG does <b>not</b> have to parse it. In all likelihood, your code already has the <i>matching declaration</i> as it is required in order for the friend function definition to be usable from pure C++ code.
</p>
<p>
The same potential problem applies to unqualified friend function declarations, such as:
</p>
<div class="code">
<pre>
class Mate {
int val;
friend int blah(); // Unqualified friend function declaration
void private_function();
public:
...
};
</pre>
</div>
<p>
Again, the actual function declaration needs to be visible to the compiler.
Or just the actual function definition as shown below.
This must be defined in the same scope as <tt>Mate</tt>.
Of course the function definition is necessary in order to avoid linking issues too.
</p>
<div class="code">
<pre>
int blah() { Mate m; m.private_function(); return m.val; }
</pre>
</div>
<H2><a name="SWIGPlus_nn18">6.10 References and pointers</a></H2>

View File

@ -38,6 +38,11 @@ public class friends_runme {
throw new Exception("failed");
if (friends.get_val1(dd) != 1.3)
throw new Exception("failed");
if (friends.chum_blah() != 1234)
throw new Exception("failed");
if (friends.mate_blah() != 4321)
throw new Exception("failed");
}
}

View File

@ -182,6 +182,32 @@ int CModelParameterCompartment::getSpeciesVal() {
}
%}
// Unqualified friend function definition and declaration example from SWIG docs
%inline %{
class Chum {
int val;
friend int chum_blah() { Chum c; c.private_function(); return c.val; }
void private_function();
};
class Mate {
int val;
friend int mate_blah(); // Unqualified friend function declaration
void private_function();
};
%}
%{
// Only seen by the compiler, not seen by SWIG
int chum_blah();
int mate_blah() { Mate m; m.private_function(); return m.val; }
void Chum::private_function() { this->val = 1234; }
void Mate::private_function() { this->val = 4321; }
%}
// Use this version with extra qualifiers to test SWIG as some compilers accept this
namespace ns1 {
namespace ns2 {

View File

@ -47,4 +47,11 @@ func main() {
if friends.Get_val1(dd).(float64) != 1.3 {
panic(0)
}
if friends.Chum_blah() != 1234 {
panic(0)
}
if friends.Mate_blah() != 4321 {
panic(0)
}
}

View File

@ -48,6 +48,11 @@ public class friends_runme {
throw new RuntimeException("failed");
if (friends.get_val1(dd) != 1.3)
throw new RuntimeException("failed");
if (friends.chum_blah() != 1234)
throw new RuntimeException("failed");
if (friends.mate_blah() != 4321)
throw new RuntimeException("failed");
}
}

View File

@ -44,3 +44,10 @@ if (friends.get_val1(di) != 4) {
if (friends.get_val1(dd) != 1.3) {
throw new Error;
}
if (friends.chum_blah() != 1234) {
throw new Error("failed");
}
if (friends.mate_blah() != 4321) {
throw new Error("failed");
}

View File

@ -25,3 +25,6 @@ d1 = f.D_i(7)
assert(f.get_val1(d1) == 7)
f.set(d1,9)
assert(f.get_val1(d1) == 9)
assert(friends.chum_blah() == 1234)
assert(friends.mate_blah() == 4321)

View File

@ -49,3 +49,10 @@ endif
if (friends.get_val1(dd) != 1.3)
error("failed");
endif
if (friends.chum_blah() != 1234)
error("failed");
endif
if (friends.mate_blah() != 4321)
error("failed");
endif

View File

@ -2,8 +2,8 @@
require "tests.php";
check::functions(array('globalscope','mix','get_val2','get_val3','bas','baz','bar','get_val1','set'));
check::classes(array('friends','Foo','A','B','D_i','D_d','CModelParameterCompartment','CModelParameterSpecies'));
check::functions(array('globalscope','mix','get_val2','get_val3','bas','baz','bar','get_val1','set','chum_blah','mate_blah'));
check::classes(array('friends','Foo','A','B','D_i','D_d','CModelParameterCompartment','CModelParameterSpecies','Chum','Mate'));
// No new vars
check::globals(array());
@ -34,4 +34,7 @@ set($dd, 1.3);
check::equal(get_val1($di), 4);
check::equal(get_val1($dd), 1.3);
check::equal(chum_blah(), 1234);
check::equal(mate_blah(), 4321);
check::done();

View File

@ -35,3 +35,8 @@ if friends.get_val1(di) != 4:
raise RuntimeError
if friends.get_val1(dd) != 1.3:
raise RuntimeError
if friends.chum_blah() != 1234:
raise RuntimeError("failed")
if friends.mate_blah() != 4321:
raise RuntimeError("failed")

View File

@ -17,3 +17,6 @@ a = Friends::A.new(2)
raise RuntimeError if Friends::get_val1(a) != 2
raise RuntimeError if Friends::get_val2(a) != 4
raise RuntimeError if Friends::get_val3(a) != 6
raise RuntimeError if Friends.chum_blah() != 1234
raise RuntimeError if Friends.mate_blah() != 4321