1

I try to reduce code duplication in a class which implements the pimpl idiom.

Imagine I have a header Foo.h. For better readability, I have reduced the methods.

class FooImp;

class Foo
{
public:
    void A(int x);
    void B(int x, double b);

private:
    FooImp* _imp;
};

Then I have an implementation file like the following:

class FooImp
{
public:
    void A(int x) {}
    void B(int x, double b) {}
};

class Logger{};
class Measure{};

void Foo::A(int x)
{
    Measure m;
    _imp->A(x);
    Logger log;
}

void Foo::B(int x, double b)
{
    Measure m;
    _imp->B(x, b);
    Logger log;
}

So around the function call, I measure something, log something and so on... The construct is always the same. I would like to have a macro that reduces the code overhead here if possible. I imagine something like this:

#define PIMPL_FUNCTION(x, ...)  \
    void Foo::x                 \
    {                           \
        Measure m;              \
        ptr->x(...);            \  
        Logger log;             \
    }                           \

PIMPL_FUNCTION(A(int x), x);
PIMPL_FUNCTION(B(int x, double b), x, b)    

Of course, this code snippet does not compile. I am unsure if what I am trying is even possible with macros. I am open to any ideas or suggestions.

A little background. All these methods are going to be API-calls for a library and the number of methods is going to increase.

RoQuOTriX
  • 2,871
  • 14
  • 25
  • 1
    Does this answer your question? [Automate pimpl'ing of C++ classes -- is there an easy way?](https://stackoverflow.com/questions/1621446/automate-pimpling-of-c-classes-is-there-an-easy-way) – Karl Knechtel Apr 19 '23 at 05:53

1 Answers1

3

MACRO's are not the first thing you should try in C++. They have a lot of downsides. Instead you should try to use (function) templates if you can.

Like in this online demo : https://onlinegdb.com/QXbtOnhYeR

And source code :

#include <iostream>
#include <memory>

class FooImp
{
public:
    void A(int x)
    {
        std::cout << "FooImp::A(" << x << ")\n";
    }

    int B(int x)
    {
        std::cout << "FooImp::B(" << x << ")\n";
        return x * x;
    }
};

class Foo
{
public:
    Foo() : 
        _imp{ std::make_unique<FooImp>() }
    {
    }

    void A(int x)
    {
        // use a lambda funtion to call _imp->A
        call_impl([&] { _imp->A(x); });
    }

    int B(int x)
    {
        // use a lambda funtion to call _imp->B to show return values work too
        return call_impl([&] { return _imp->B(x); });
    }

private:
    // No MACRO use a function template 
    // see lamdba functions https://en.cppreference.com/w/cpp/language/lambda
    // decltype(fn()) = return type of function fn
    template<typename fn_t>
    auto call_impl(fn_t fn) -> decltype(fn()) // pass a function object (lambda/std::function)
    {
        std::cout << "Construct Measure here\n";
        

        if constexpr (std::is_same_v<void, decltype(fn()) >)
        {
            fn();
            std::cout << "Construct Log here\n";
        }
        else
        {
            auto retval = fn();
            std::cout << "Construct Log here\n";
            return retval;
        }
    }

    std::unique_ptr<FooImp> _imp; // do NOT use raw pointers
};

int main()
{
    Foo foo;
    foo.A(1);
    std::cout << foo.B(2) << "\n";
    return 0;
}
Pepijn Kramer
  • 9,356
  • 2
  • 8
  • 19
  • That solution is awesome!! Thanks a lot. My next question would have been how to handle return values because I also need to log them. With this solution, this is also possible. Great answer! – RoQuOTriX Apr 19 '23 at 11:57
  • RAII class might allow to get rid of condition [Demo](https://godbolt.org/z/oo5xb5vKz). – Jarod42 Apr 19 '23 at 12:58