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.
CodePudding user response:
In c 20, no. But in c 23, actually yes, thanks to deducing this. I guess this is yet another discovered use case of that facility (of which I am a coauthor).
The idea of the feature is that you can take your member function like:
struct DoubleWithProbe {
operator double() const;
};
and rewrite it from having an implicit object parameter to having an explicit object parameter (in this case by convention named self
, the this
keyword indicates that it is the explicit object parameter):
struct DoubleWithProbe {
operator double(this DoubleWithProbe const& self);
};
But there's no requirement that this parameter must have the same type as the class that it's in. You could, instead, create a type like this (note that RefWithLoc<T>
is implicitly constructible from a T&
):
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)
{ }
};
And then use that as the explicit object parameter type:
struct DoubleWithProbe {
operator double(this RefWithLoc<DoubleWithProbe const> self);
};
And now, from within the body, self.t
is a DoubleWithProbe
(a reference to the same object that, in the original implementation, this
would have pointed to) but you also get self.loc
as desired.
CodePudding user response:
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)
{
//...
}
};