How can I log the function name and line number whenever a specific struct field changes, without manually write “log this field” in every line [migrated]

I have a large C++ project involving a specific data structure. Throughout the program’s execution, certain field within this structure are modified multiple times according to a specific algorithm.

I want to monitor a particular field by logging the following information:

1. The function where the assignment to this field took place

2. The line number

In other words, I want to modify the header file (.h) containing this structure by adding something that logs the function name and line number whenever the field in question is updated.

I tried asking an AI for a solution, but it gave me complete nonsense that didn’t work at all, something like this:

#include <sys/neutrino.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <algorithm>
#include <cmath>

template <typename T, unsigned int ParamID>
struct MonitoredField {
    T value;

    operator T() const { return value; }
    operator T&() { return value; }

    MonitoredField& operator+=(const T& RHS) { value = value + RHS; return *this; }
    MonitoredField& operator-=(const T& RHS) { value = value - RHS; return *this; }

    MonitoredField& operator=(const T& newValue) {
        return set_value(newValue, __builtin_FILE(), __builtin_LINE(), __builtin_FUNCTION());
    }

private:
    MonitoredField& set_value(const T& newValue, const char* f, int l, const char* fn) {
        T oldValue = value;
        value = newValue;

        log_printff(DebugLog, "%s:%d: [MUTATION] ID:%d | paramId: %d | FUNC: %s() | %g -> %gn",
                    f, l, ParamID, ParamID, fn,
                    (double)oldValue, (double)value);

        return *this;
    }
};

namespace std {
    template<typename T, unsigned int ID>
    inline double min(double a, const MonitoredField<T, ID>& b) { return (a < (double)b.value) ? a : (double)b.value; }
    template<typename T, unsigned int ID>
    inline double min(const MonitoredField<T, ID>& a, double b) { return ((double)a.value < b) ? (double)a.value : b; }
    template<typename T, unsigned int ID>
    inline double max(double a, const MonitoredField<T, ID>& b) { return (a > (double)b.value) ? a : (double)b.value; }
    template<typename T, unsigned int ID>
    inline double max(const MonitoredField<T, ID>& a, double b) { return ((double)a.value > b) ? (double)a.value : b; }
}

As a result, the following information is logged:


20:11:01:046385 ../../../share/structer.h:39: [MUTATION] ID:1 | paramId: 1 | FUNC: operator=()
20:11:01:046385 ../../../share/structer.h:39: [MUTATION] ID:3 | paramId: 3 | FUNC: operator=()
20:11:01:046385 ../../../share/structer.h:39: [MUTATION] ID:2 | paramId: 2 | FUNC: operator=()
20:11:01:046385 ../../../share/structer.h:39: [MUTATION] ID:1 | paramId: 1 | FUNC: operator=()