Can c++ std::source_location be used with a conversion operator?

Viewed 398

I'm trying to add debug instrumentation in C++. I want to probe all changes to a variable. To do that, I'm using the c++20 std::source_location::current() feature, like this member function:

void DoubleWithProbe::set(double newValue, std::source_location loc = std::source_location::current())
{
    std::cout << "Changed: new = " << newValue << ", old = " << m_value
        << " (" << loc.file_name() << ":"
        << loc.line() << ")" << std::endl;
    m_value = newValue;
}

However, this requires the caller to use a set function. If I try to use operators, the pattern fails:

operator double(std::source_location loc = std::source_location::current()) const
{
    std::cout << "Accessed: value = " << m_value <<
            << " (" << loc.file_name() << ":"
            << loc.line() << ")" << std::endl;
    return m_value;
}
DoubleWithProbe& operator=(double newValue, std::source_location loc = std::source_location::current())
{
    ...
}

These are both illegal because operator double may not have arguments and operator= must have only one argument. For operator=, I've found this workaround:

    struct DoubleAndLoc
    {
        double m_val;
        std::source_location m_loc;
        // not explicit.
        DoubleAndLoc(double val, std::source_location loc = std::source_location::current())
            : m_val(val), m_loc(loc)
        {
        }
    };
    DoubleWithProbe& operator=(DoubleAndLoc newValue)
    {
        ...
    }

This is probably ok for doubles, but for strings, this would quickly run into double-conversion issues.

Is there any way to make operator double() work, and any way to make operator=() work more cleanly?

A working example is at https://godbolt.org/z/4Ea1sPT1n.

2 Answers

In , no. But in ... still no.


A previous version of this answer used deducing this to be able to declare the conversion function like this:

template <typename T>
struct RefWithLoc {
    T& t;
    std::source_location loc;

    RefWithLoc(T& t, std::source_location loc = std::source_location::currenct())
      : t(t)
      , loc(loc)
    { }
};

struct DoubleWithProbe {
    operator double(this RefWithLoc<DoubleWithProbe const> self);
};

However, this... doesn't work. [over.best.ics.general]/4 prohibits considering user-defined conversion sequences for the object parameter of a user-defined conversion function (on the principle that at most one conversion is allowed to happen).

With the above you could still write dwp.operator double(), but not static_cast<double>(dwp).

Perhaps the fact that you can easily get this to work with a named conversion (even in ) suggests that if the functionality is required, then you could just add a to_double() function?

You can generalise your DoubleAndLoc, and then use a member function overloads instead of operator =.

template <typename T>
struct LocatedValue
{
    T m_val;
    std::source_location m_loc;
    // not explicit.
    LocatedValue(T val, std::source_location loc = std::source_location::current())
        : m_val(std::forward<T>(val)), m_loc(loc)
    {
    }
};

class StringWithProbe {
public:
    StringWithProbe& operator=(LocatedValue<const char *> newValue)
    {
        assign(newValue.m_val, newValue.m_loc);
        return *this;
    }
    StringWithProbe& operator=(LocatedValue<std::string> newValue)
    {
        assign(newValue.m_val, newValue.m_loc);
        return *this;
    }

private:
    void assign(const char * value, std::source_location loc)
    {
        //...
    }
    void assign(std::string value, std::source_location loc)
    {
        //...
    }
};

See it on coliru

Related