Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Virtual method with variadic arguments

I am trying to implement a logging system abstracted behind a locator service (in the style of this guide) in such a way that the logging system can be subclassed for various different logging situations. I would prefer to be able to use printf style format strings instead of <<, but that means supporting variable number of arguments. Variadic templates can easily solve this, however, they cannot be virtual, which means that the base logging class cannot be abstract (as an interface).

Ideally what I'd need is some way to have the parent method not templated but just accepting of a parameter pack which it would forward to the right (templated) child method. I've mostly seen two ways of doing this, one which is using va_list which apparently is unsafe, complicated and not really meant to interact with variadic templates easily, and CRTP, which would work but means that no pointer can be declared of the abstract base class in the locator object without knowing the subclass type, which defeats the purpose.

Here is example code assuming that virtual templates were a thing:

class Logger
{
    public:
        template <typename ... Args>
        virtual void print(char *filename, int line, std::string &&msg, Args&& ... args) = 0;

    protected:
        template <typename ... Args>
        std::string format(char *filename, int line, std::string &msg, Args&& ... args)
        {
            std::string output = "%s at line %i: " + msg;
            int size = std::snprintf(nullptr, 0, output.c_str());
            // +1 for null termination
            std::vector<char> buf(size + 1);
            std::snprintf(&buf[0], buf.size(), output.c_str(), filename, line, args...);
            return std::string(&buf[0]);
        }


};

class PrintLogger : public Logger
{
    public:
        template <typename ... Args>
        void print(char *filename, int line, std::string &&msg, Args&& ... args)
        {
            std::cout << format(filename, line, msg, args...);
        }
};

Here is example code with the CRTP solution (which breaks the locator):

template <typename Loggertype>
class Logger
{
    public:
        template <typename ... Args>
        void print(char *filename, int line, std::string &&msg, Args&& ... args)
        {
            Loggertype::print(filename, line, msg, args...);
        }

    protected:
        template <typename ... Args>
        std::string format(char *filename, int line, std::string &msg, Args&& ... args)
        {
            std::string output = "%s at line %i: " + msg;
            int size = std::snprintf(nullptr, 0, output.c_str());
            // +1 for null termination
            std::vector<char> buf(size + 1);
            std::snprintf(&buf[0], buf.size(), output.c_str(), filename, line, args...);
            return std::string(&buf[0]);
        }


};

class PrintLogger : public Logger<PrintLogger>
{
    public:
        template <typename ... Args>
        void print(char *filename, int line, std::string &&msg, Args&& ... args)
        {
            std::cout << format(filename, line, msg, args...);
        }
};

And here is the locator:

class Global {
    public:
        static void initialize() { logger = nullptr; }
        static Logger& logging()
        {
            if (logger == nullptr)
            {
                throw new std::runtime_error("Attempt to log something before a logging instance was created!");
            }
            return *logger;
        }
        static void provide_logging(Logger *new_logger) { logger = new_logger; }
    private:
        static Logger *logger;
};
like image 603
Orpheon Avatar asked Oct 30 '22 09:10

Orpheon


1 Answers

You have to handle 2 issue:

  1. iterate on packed argument
  2. Call a virtual function with template argument.

The first issue you need is handle by recursion function The second I used with boost::any

class Logger
{
public:


    template <typename ... Args>
    std::string print(Args&& ... args)
    {
        start_format();
        interate_on(args...);
        return end_format();
    }
protected:



    template <typename Arg>
    void interate_on(Arg&& arg)
    {
        print(arg);
    }


    template <typename Arg, typename ... Args>
    void interate_on(Arg&& arg, Args&& ... args)
    {
        print(arg);
        interate_on(std::forward<Args>(args)...);
    }

    virtual void start_format() = 0;

    virtual std::string end_format() = 0;

    virtual void print(boost::any& value) = 0;
};

class PrinterLogger : public Logger
{
protected:
    virtual void start_format()
    {

    }

    virtual std::string end_format()
    {
        return std::string();
    }

    virtual void print(boost::any& value)
    {
        // accumulate printf

    }
};
like image 89
alangab Avatar answered Nov 15 '22 05:11

alangab