Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to self-document a callback function that is called by template library class?

I have a function User::func()(callback) that would be called by a template class (Library<T>).

In the first iteration of development, everyone know that func() serves only for that single purpose.
A few months later, most members forget what func() is for.
After some heavy refactoring, the func() is sometimes deleted by some coders.

At first, I didn't think this is a problem at all.
However, after I re-encountered this pattern several times, I think I need some counter-measure.

Question

How to document it elegantly? (cute && concise && no additional CPU cost)

Example

Here is a simplified code:-
(The real world problem is scattering around 10+ library-files & 20+ user files & 40+ functions.)

Library.h

template<class T> class Library{
    public: T* node=nullptr;
    public: void utility(){
        node->func();  //#1
    }
};

User.h

class User{
    public: void func(){/** some code*/} //#1
    //... a lot of other functions  ...
    // some of them are also callback of other libraries
};

main.cpp

int main(){
    Library<User> li; .... ;  li.utility();
}

My poor solutions

1. Comment / doc

As the first workaround, I tend to add a comment like this:-

class User{ 
    /** This function is for "Library" callback */
    public: void func(){/** some code*/}
};

But it gets dirty pretty fast - I have to add it to every "func" in every class.

2. Rename the "func()"

In real case, I tend to prefix function name like this:-

class User{ 
    public: void LIBRARY_func(){/** some code*/}
};

It is very noticeable, but the function name is now very longer.
(especially when Library-class has longer class name)

3. Virtual class with "func()=0"

I am considering to create an abstract class as interface for the callback.

class LibraryCallback{ 
    public: virtual void func()=0;
};
class User : public LibraryCallback{ 
    public: virtual void func(){/** some code*/}
};

It provides feeling that func() is for something-quite-external. :)
However, I have to sacrifice virtual-calling cost (v-table).
In performance-critical cases, I can't afford it.

4. Static function

(idea from Daniel Jour in comment, thank!)

Almost 1 month later, here is how I use :-

Library.h

template<class T> class Library{
    public: T* node=nullptr;
    public: void utility(){
        T::func(node);  //#1
    }
};

User.h

class User{
    public: static void func(Callback*){/** some code*/} 
};

main.cpp

int main(){
    Library<User> li;
}

It is probably cleaner, but still lack self-document.

like image 419
javaLover Avatar asked Apr 17 '17 04:04

javaLover


People also ask

How do you write a callback function?

A custom callback function can be created by using the callback keyword as the last parameter. It can then be invoked by calling the callback() function at the end of the function. The typeof operator is optionally used to check if the argument passed is actually a function. console.

How do you read a callback function?

A callback function is a function passed into another function as an argument, which is then invoked inside the outer function to complete some kind of routine or action. The above example is a synchronous callback, as it is executed immediately.

How do you define a callback function in C++?

In simple language, If a reference of a function is passed to another function as an argument to call it, then it will be called as a Callback function. In C, a callback function is a function that is called through a function pointer. In C++ STL, functors are also used for this purpose.

How do you write a callback function in C?

Example Code #include<stdio. h> void my_function() { printf("This is a normal function."); } void my_callback_function(void (*ptr)()) { printf("This is callback function.


3 Answers

func is not a feature of User. It is a feature of the User-Library<T> coupling.

Placing it in User if it doesn't have clear semantics outside of Library<T> use is a bad idea. If it does have clear semantics, it should say what it does, and deleting it should be an obviously bad idea.

Placing it in Library<T> cannot work, because its behavior is a function of the T in Library<T>.

The answer is to place it in neither spot.

template<class T> struct tag_t{ using type=T; constexpr tag_t(){} };
template<class T> constexpr tag_t<T> tag{};

Now in Library.h:

struct ForLibrary;
template<class T> class Library{
  public: T* node=nullptr;
  public: void utility(){
    func( tag<ForLibrary>, node ); // #1
  }
};

in User.h:

struct ForLibrary;
class User{ 
/** This function is for "Library" callback */
public:
  friend void func( tag_t<ForLibrary>, User* self ) {
    // code
  }
};

or just put this into the same namespace as User, or the same namespace as ForLibrary:

friend func( tag_t<ForLibrary>, User* self );

Before deleting func, you'll track down ForLibrary.

It is no longer part of the "public interface" of User, so doesn't clutter it up. It is either a friend (a helper), or a free function in the same namespace of either User or Library.

You can implement it where you need a Library<User> instead of in User.h or Library.h, especially if it just uses public interfaces of User.

The techniques used here are "tag dispatching", "argument dependent lookup", "friend functions" and preferring free functions over methods.

like image 65
Yakk - Adam Nevraumont Avatar answered Oct 29 '22 11:10

Yakk - Adam Nevraumont


From the user side, I would use crtp to create a callback interface, and force Users to use it. For example:

template <typename T>
struct ICallbacks
{
    void foo() 
    {
        static_cast<T*>(this)->foo();
    }
};

Users should inherit from this interface and implement foo() callback

struct User : public ICallbacks<User>
{
    void foo() {std::cout << "User call back" << std::endl;}
};

The nice thing about it is that if Library is using ICallback interface and User forget to implement foo() you will get a nice compiler error message.

Note that there is no virtual function, so no performance penalty here.

From the library side, I would only call those callbacks via its interfaces (in this case ICallback). Following OP in using pointers, I would do something like this:

template <typename T>
struct Library
{
    ICallbacks<T> *node = 0;

    void utility()
    {
       assert(node != nullptr);
       node->foo();
    }
};

Note that things get auto documented in this way. It is very explicit that you are using a callback interface, and node is the object who has those functions.

Bellow a complete working example:

#include <iostream>
#include <cassert>

template <typename T>
struct ICallbacks
{
    void foo() 
   {
       static_cast<T*>(this)->foo();
   }
};

struct User : public ICallbacks<User>
{
     void foo() {std::cout << "User call back" << std::endl;}
};

template <typename T>
struct Library
{
    ICallbacks<T> *node = 0;

    void utility()
    {
        assert(node != nullptr);
        node->foo();
    }
};

int main()
{
    User user;

    Library<User> l;
    l.node = &user;
    l.utility();
}
like image 23
Amadeus Avatar answered Oct 29 '22 13:10

Amadeus


Test.h

#ifndef TEST_H
#define TEST_H

// User Class Prototype Declarations
class User;

// Templated Wrapper Class To Contain Callback Functions
// User Will Inherit From This Using Their Own Class As This
// Class's Template Parameter
template <class T>
class Wrapper {
public:
    // Function Template For Callback Methods. 
    template<class U>
    auto Callback(...) {};
};

// Templated Library Class Defaulted To User With The Utility Function
// That Provides The Invoking Of The Call Back Method
template<class T = User>
class Library {
public:
    T* node = nullptr;
    void utility() {
        T::Callback(node);
    }
};

// User Class Inherited From Wrapper Class Using Itself As Wrapper's Template Parameter.
// Call Back Method In User Is A Static Method And Takes A class Wrapper* Declaration As 
// Its Parameter
class User : public Wrapper<User> {
public:
    static void Callback( class Wrapper* ) { std::cout << "Callback was called.\n";  }
};

#endif // TEST_H

main.cpp

#include "Test.h"

int main() {

    Library<User> l;
    l.utility();

    return 0;
}

Output

Callback was called.

I was able to compile, build and run this without error in VS2017 CE on Windows 7 - 64bit Intel Core 2 Quad Extreme.

Any Thoughts?

I would recommend to name the wrapper class appropriately, then for each specific call back function that has a unique purpose name them accordingly within the wrapper class.



Edit

After playing around with this "template magic" well there is no such thing... I had commented out the function template in the Wrapper class and found that it is not needed. Then I commented out the class Wrapper* that is the argument list for the Callback() in User. This gave me a compiler error that stated that User::Callback() does not take 0 arguments. So I looked back at Wrapper since User inherits from it. Well at this point Wrapper is an empty class template.

This lead me to look at Library. Library has a pointer to User as a public member and a utility() function that invokes User's static Callback method. It is here that the invoking method is taking a pointer to a User object as its parameter. So it lead me to try this:

class User; // Prototype
class A{}; // Empty Class

template<class T = User>
class Library {
public: 
    T* node = nullptr;
    void utility() {
        T::Callback(node);
    }
};

class User : public A {
public:
    static void Callback( A* ) { std::cout << "Callback was called.\n"; }
};

And this compiles and builds correctly as the simplified version. However; when I thought about it; the template version is better because it is deduced at compile time and not run time. So when we go back to using templates javaLover had asked me what class Wrapper* means or is within the argument list for the Callback method within the User class.

I'll try to explain this as clearly as I can but first the wrapper Class is just an empty template shell that User will inherit from and it does nothing but act as a base class and it now looks like this:

template<class T>
class Wrapper {   // Could Be Changed To A More Suitable Name Such As Shell or BaseShell
};

When we look at the User class:

class User : public Wrapper<User> {
public:
    static void Callback( class Wrapper* ) { // print statement }
};

We see that User is a non-template class that inherits from a template class but uses itself as the template's argument. It contains a public static method and this method doesn't return any thing but it does take a single parameter; this is also evident in the Library class that has its template parameter as a User class. When the Library's utility() method invokes User's Callback() method the parameter that the Library is expecting is a pointer to a User object. So when we go back to the User class instead of declaring it as a User* pointer directly in its declaration I'm using the empty class template that it inherits from. However if you try to do this:

class User : public Wrapper<User> {
public:
    static void Callback( Wrapper* ) { // print statement }
};

You should get a message that Wrapper* is missing it's argument list. We could just do Wrapper<User>* here but that is redundant since we already see that User is inheriting from Wrapper that takes itself. So we can fix this and make it cleaner just by prefixing the Wrapper* with the class keyword since it is a class template. Hence the template magic... well there is no magic here... just compiler intrinsic and optimizations.

like image 38
Francis Cugler Avatar answered Oct 29 '22 12:10

Francis Cugler