19

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.

javaLover
  • 6,347
  • 2
  • 22
  • 67
  • One advantage of the solution with an abstract base class is that it allows you to declare `LibraryCallback* node`. – Code-Apprentice Apr 17 '17 at 04:39
  • @Code-Apprentice Thank. It is useful. However, in this case, I don't need it. I can do it a bit less elegant way with `T* t` inside `class Library{}`. I accept that virtual class provides a great convenience when coding `Library`. – javaLover Apr 17 '17 at 04:41
  • 2
    One thing that I don't really understand: removing the member function should lead to a compiler error ... isn't that "documentation" enough? If the member function of library isn't instantiated from your library code you could add a unit test which does. – Daniel Jour Apr 17 '17 at 07:19
  • @Daniel Jour Yes, it is quite sufficient. Nonetheless, it would be better if the code have a form/shape that noticeable by itself e.g. when scrolling in editor without any (probably long) compiling. – javaLover Apr 17 '17 at 07:39
  • 1
    There can be multiple, different `User` classes (with different `func` implementation), right? If it is not immediately obvious that `func` "has any use and thus gets removed" ... then perhaps it really would be better to have `func` not as a member function of `User`, but perhaps as a free function or member of some "helper" class? – Daniel Jour Apr 18 '17 at 07:17
  • @Daniel Jour Yes, it can have different implementation. Helper class is possible. Its disadvantage is that I have to pass the helper class / free function as another template parameter `Library` or `Library` which is a little dirtier. .... But still, I think your idea is handy & unique. ..... If you post it, I will upvote it. Thank! XD – javaLover Apr 18 '17 at 07:24
  • Does `User::func()` need access to non-public functions of `User`? Otherwise, a traits-class might be an option (separates implementation from implementation of `User` and makes it obvious that this is a connection between `User` and `Library`). – chtz May 17 '17 at 11:02
  • @chtz I have various situation. However, it is mostly (70%) no. Even in that harder case, I think I can by-pass the restriction by using the "friend"-keyword, and the code will still look sensible. – javaLover May 17 '17 at 11:06

9 Answers9

5

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • This technique (tag) is advance for me. Can I really pass `tag_t` as a type? It is not an instance of object! (`tag_t()`) ...... I will need some time to find & read about it, thank. – javaLover May 12 '17 at 02:05
  • 1
    @javaLover `tag_t` is a type. `tag` is a value of type `tag_t`. You could use `tag_t{}` to make a value instead. – Yakk - Adam Nevraumont May 12 '17 at 11:54
  • Oh, my bad. I understand it now. By the way, this solution costs a little bit from an extra parameter passing, right? I know that even there are some cost, it will be very little, but I want to know. – javaLover May 13 '17 at 01:29
  • 1
    @javaLover In C++, parameter passing happens in the abstract machine; at runtime, it can evaporate if the passing involves trivially copyable objects (pointers, tags, integers, etc), structs of same, or if the inlined non-trivial move/copies can be eliminated by the compiler. I see no reason why at any non-debug optimization level there would be any overhead here. – Yakk - Adam Nevraumont May 13 '17 at 11:28
  • Note that `virtual` calls won't be optimizable away. But the overhead of passing a pointer and a 1 byte tag is going to be difficult to detect. – Yakk - Adam Nevraumont May 13 '17 at 11:55
  • Variable template not supported in VC++, so I can't make it cute & short. ( evidence http://rextester.com/MEMEQ66100 ) Do you happen to know some nice workaround besides `tag_t()`? It would be nice if I can omit the parenthesis `()`. – javaLover May 14 '17 at 09:29
  • Do you happen to know how to fix a similar issue that occurs on "typedef"? http://stackoverflow.com/questions/44109929 (new question) – javaLover May 22 '17 at 10:05
4

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();
}
Amadeus
  • 10,199
  • 3
  • 25
  • 31
  • I would get ugly warning about `User` try to hide `foo()` from parent (`ICallbacks`). Is there a way to avoid the warning (except using `#pragma disable warning`)? – javaLover May 12 '17 at 01:58
  • @javaLover I was too careless about warning messages, sorry. I edited my answer to eliminate those warnings, while trying to keep the original context – Amadeus May 12 '17 at 04:01
  • If `User` does not implement `foo()` this will lead to infinite recursion (at runtime)! – chtz May 17 '17 at 10:57
3

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.

Community
  • 1
  • 1
Francis Cugler
  • 7,788
  • 2
  • 28
  • 59
  • Change `T` in `template auto Callback(...) {};` to `T2`? (It is currently almost compliable http://ideone.com/7Ueuul) .... By the way, what is that `Callback( class Wrapper* )`? i.e. how come can you use `class Wrapper*` as a function parameter? That is new to me. What is its advantage for this solution (i.e. why you use it)? – javaLover May 12 '17 at 09:33
  • @javaLover to be honest with you; I didn't expect that to work but it does; must be template magic. – Francis Cugler May 12 '17 at 09:43
  • @javaLover ...I think because of the fact that User inherits from Wrapper and Wrapper has User as its template parameter. The Callback method that belongs to User which is a static method has as its parameter argument as a class pointer declaration since Wrapper is a class template. It would be redundant to have `Wrapper` as its argument. The Wrapper class doesn't have any instantiation of a class instance but it has public function templates. – Francis Cugler May 12 '17 at 09:45
  • @javaLover... I kind of stumbled on to this little trick or hack with the templates; at first I was thinking of trying to use a different kind of template wrapper class that contains lambdas but I couldn't seem to get it to work properly. – Francis Cugler May 12 '17 at 09:47
  • @javaLover It compiles and runs on windows 7 using MSVS 2017 CE. Not sure if it will compile on Clang, GCC or Mingw. – Francis Cugler May 12 '17 at 09:51
  • @javaLover I had commented out the function template in the wrapper class and it still compiles, builds, run and still invokes User::Callback(). – Francis Cugler May 12 '17 at 09:56
  • @javaLover I had also commented out the `class Wrapper*` that is the argument list for the Callback method within `User` and when I did; it said that User::Callback doesn't take 0 arguments. So I traced back to look at `Wrapper` and it was just an empty body class. So then I looked at the `Library` class and it has as it's member a `T*` which will be a `User*` object. This is invoking `User` to call its call back method with a `User*` passed to it. Since User is inherited from `Wrapper` it will accept the class declaration pointer as its parameter. – Francis Cugler May 12 '17 at 10:01
  • 1. Yes, after edit, your code also works [fine in gcc](http://ideone.com/GFjfHv). I think your trick similar as [Yakk's tag solution](http://stackoverflow.com/a/43919854). It might be a standard-compliant way to pass type as parameter. (!?!) 2. You should remove `;` in `auto Callback(...) {};`. I got the compile error in coliru. 3. Can I remove the word `class` in `static void Callback( class Wrapper* )`? It would still be compilable. (http://coliru.stacked-crooked.com/a/1beafc04731d0aee) – javaLover May 12 '17 at 10:04
  • @javaLover; I made a major edit to the answer you should check it out; the `Wrapper` class that is a class template only needs to be an empty shell for it to work. The base class can not be an incomplete type, but it can be an empty shell. – Francis Cugler May 12 '17 at 10:29
3

While I know that I don't answer your specific question (how to document the not-to-be-deleted function) I would solve your problem (keeping the seemingly unused callback function in the code base) by instantiating Library<User> and calling the utility() function in a unit test (or maybe it should rather be called an API test...). This solution would probably scale to your real world example too, as long as you don't have to check each possible combination of library classes and callback functions.

If you are lucky enough to work in an organization where successful unit tests and code review are required before changes go into the code base this would require a change to the unit tests before anyone could remove the User::func() function and such a change would probably catch the attention of a reviewer.

Then again, you know your environment and I don't, and I'm aware that this solution doesn't fit all situations.

psyill
  • 891
  • 6
  • 10
2

Here is a solution using a Traits class:

// Library.h:
template<class T> struct LibraryTraits; // must be implemented for every User-class

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

// User.h:
class User { };

// must only be implemented if User is to be used by Library (and can be implemented somewhere else)
template<> struct LibraryTraits<User> {
    static void func(User* node) { std::cout << "LibraryTraits<User>::func(" << node << ")\n"; }
};

// main.cpp:

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

Advantages:

  • It is obvious by the naming that LibraryTraits<User> is only required for interfacing User by Library (and can be removed, once either Library or User gets removed.
  • LibraryTraits can be specialized independent of Library and User

Disadvantages:

  • No easy access to private members of User (making LibraryTraits a friend of User would remove the independence).
  • If the same func is needed for different Library classes multiple Trait classes need to be implemented (could be solved by default implementations inheriting from other Trait classes).
chtz
  • 17,329
  • 4
  • 26
  • 56
  • It is stunning that even there are a lot of solutions in place, you can still provide another unique one with its own advantage. Thank. ... By the way, I don't think the disadvantage you listed are really disadvantage at all. – javaLover May 17 '17 at 12:46
2

This heavily reminds an old good Policy-Based Design, except in your case you do not inherit the Library class from the User class. Good names are the best friends of any API. Combine this and the well-known patter of Policy-Based Design (well-known is very important because the class names with the word Policy in it will immediately ring the bell in many readers of the code) and, I assume, you get a well self-documenting code.

  • Inheritance won't give you any performance overhead, but will give you an ability to have the Callback as a protected method, that will give some hint that it is supposed to be inherited and be used somewhere.

  • Have clearly standing-out and consistent naming among multiple User-like classes (e.g. SomePolicyOfSomething in the manner of aforementioned Policy-Based Design), as well as, the template arguments for the Library (e.g SomePolicy, or I would call it TSomePolicy).

  • Having using declaration of the Callback in the Library class might give much clearer and earlier errors (e.g. from IDE, or modern clang, visial studio syntax parsers for IDE).

Another arguable option might be a static_assert if you have C++>=11. But in this case it must be used in every User-like class ((.

Yuki
  • 3,857
  • 5
  • 25
  • 43
1

Not a direct answer to your question on how to document it, but something to consider:

If your Library template requires an implementation of someFunction() for each class to be used in it, i'd recommend adding it as a template argument.

#include <functional>
template<class Type, std::function<void(Type*)> callback>
class Library {
    // Some Stuff...
    Type* node = nullptr;
public:
    void utility() {
         callback(this->node);
    }
};

Might make it even more explicit, so that other devs know it's needed.

TinfoilPancakes
  • 238
  • 1
  • 13
  • Sadly, the problematic devs are the ones that blindly code `User.h` and [don't have time / miss / have no incentive] to look into `Library.h`. They will still not get any clue. – javaLover May 17 '17 at 01:50
  • True, but just a suggestion for the future :) Also if they do happen to delete the function in User you can still pass one in, regardless of having one in the class. – TinfoilPancakes May 17 '17 at 21:58
0

abstract class is the best way to enforce the function not to be deleted. So i recommend implementing the base class with pure virtual function, so that derived has to define the function. OR second solution would be to have function pointers so that performance will be saved by avoiding extra overhead of V-table creation and calling.

rams time
  • 179
  • 5
  • Your first solution is same as "Virtual class with func()=0", isn't it? How can your second solution reduce human error? How does it promote better readability? – javaLover Apr 17 '17 at 09:00
  • This solution seems to has the potential (might introduce a new solution with unique pro & cons). However, some a snippet or more detail is still needed. – javaLover Apr 18 '17 at 01:56
0

If it is not obvious that func() is needed in User, then I'd argue you're violating the single responsibility principle. Instead create an adapter class of which User as a member.

class UserCallback {
  public:
    void func();

  private:
    User m_user;
}

That way the existance of UserCallback documents that func() is an external call back, and separates out Library's need of a callback from the actual responsibilities of User.

dlasalle
  • 1,615
  • 3
  • 19
  • 25