2

Given the following, working code.

#include <iostream>

template<class Detail>
class AbstractLogger
{
public:
    static void log(const char* str) {
        Detail::log_detailled(str);
    }
};

class Logger : public AbstractLogger<Logger>
{
public:
    static void log_detailled(const char* str) {
        std::cerr << str << std::endl;
    }
};

int main(void)
{
    AbstractLogger<Logger>::log("main function running!");
    return 0;
}

Now, I want to put AbstractLogger into a library, and let the library user define his own logger, like the Logger class here. This has one drawback: AbstractLogger<Logger> can not be used inside the library, since the library can not know Logger.

Notes:

  • Please no virtual functions or questions why not. Also, I am aware of the similar problem that "static virtual" members are invalid. Maybe, there is a workaround in CRTP :)
  • C++11 will be interesting, however, I need "usual" C++.
Johannes
  • 2,901
  • 5
  • 30
  • 50
  • 2
    What do you mean by *I want to put `AbstractLogger` into a library*. Do you mean you want to *use* it in the library without knowing the instantiating type? Or do you want some sort of generated code to be in the library? or...? – David Rodríguez - dribeas Oct 12 '12 at 19:51
  • Smells like a heavy design flaw and abuse of the CRT pattern ... – πάντα ῥεῖ Oct 12 '12 at 19:58
  • Templates are compiled into concrete class representations when they are defined in code. If you define an `AbstractLogger` and an `AbstractLogger`, two versions will be compiled. In a library, you have to define what is available beforehand. – Steztric Oct 12 '12 at 20:10

3 Answers3

2

If what you mean is that you want to have a library that uses this as a logging mechanism without knowing the exact instantiating type, I would advice against it.

The only way of doing it while meeting your other requirements (i.e. no virtual functions) is that all your functions/types in the library that need to log are converted into templates that take the Logger type. The net result is that most of your interface becomes a template (although you can probably move a good amount of the implementation to non-templated code, it will make your life much harder than needed, and it will still generate a much larger binary).

If your concern with virtual functions is performance, then you should reconsider your approach and the problems it brings. In particular, logging is expensive. Most logging libraries tackle it by optimizing the non-logging case (by means of macros that avoid calling the logger if the log level/group/... are not enabled), but still leave dynamic dispatch for the actual writting. The cost of the dynamic dispatch is negligible compared with the cost of writing to the console, or a file, or even with the cost of generating the message that will be logged (I am assuming that you not only log literal strings)

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
1

The usual approach is to code against a concept, while providing helpers so that users may easily produce types that satisfy one or more of those concepts. As an example, something like boost::iterator_facade is a CRTP helper that makes it easier for a user to write an iterator. Then, that iterator can be used anywhere an iterator is accepted -- for instance in the range constructor of std::vector. Notice how that particular constructor has no foreknowledge of the user-defined type.

In your case, AbstractLogger would be the CRTP helper. The missing piece would be to define e.g. a logger concept. As a result, notice that everything that needs a logger either needs to be implemented as a template or you need a type-erasing container to hold arbitrary loggers.

Concept checks (like those provided by Boost) are convenient for this kind of programming, since they allow to represent a concept with actual code.

Luc Danton
  • 34,649
  • 6
  • 70
  • 114
  • Thanks for your answer. Can you please outline shortly how such a "type erasing container" could look like? – Johannes Oct 13 '12 at 06:48
  • @Johannes It would be very similar to `std::function`, which is a container for anything that is Callable with signature `R(A...)`. So it would have a constructor `template container(Logger log);` taking anything conforming to a logger, and would itself conform to the Logger concept -- delegating actual operations to the logger that was passed at construction time. [This](http://stackoverflow.com/questions/5450159/type-erasure-techniques) outlines some type-erasure techniques. – Luc Danton Oct 13 '12 at 11:37
0

Template classes can't be 'put in a library' since they are instantiated by the compiler as specializations of their template parameters.

You may put parameter independent stuff used in the template implementation into a library though.

πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190