so I'm trying to design a class Observed<T>
, which stores a T
and bool
, with the boolean value indicating whether the T
member variable has been updated since it was last set. To demonstrate, my initial implementation was something like
template <std::copyable T>
class Observed
{
public:
Observed()
: m_data{}, m_updated{false} {};
Observed(const T &data)
: m_data{data}, m_updated{true} {};
// .. move, copy, assignment ctors
// setting the data leaves it 'updated'
template <typename U>
requires(std::same_as<T, U>)
auto write(U &&value) -> void
{
m_data = std::forward<U>(value);
m_updated = true;
}
// to test if it has since been modified (i.e. written without reading)
auto modified() -> bool
{
return m_updated;
}
// reading the value means it has no longer been updated
auto value() -> T &
{
m_updated = false;
return m_data;
}
private:
T m_data;
bool m_updated;
};
However, the problem became that for an example Observed<std::vector<int>> v
, if I needed to add an element to the vector i'd call v.value().push_back(...)
, which would surpass the write()
function, and subsequently m_updated
would not be set correctly.
My slightly janky approach was to therefore introduce a call()
function as follows:
template <std::copyable T>
class Observed
{
public:
// ctors
Observed()
: m_data{}, m_updated{false} {};
Observed(const T &data)
: m_data{data}, m_updated{true} {};
// the new 'call' function
template<typename Func, typename... Ts>
auto call(Func&& func, Ts... args) -> std::invoke_result_t<Func, Ts...>
{
m_updated = true;
return std::invoke(func, m_data, args...);
}
// .. rest of class
private:
T m_data;
bool m_updated;
};
so that you'd be able to write v.call(&std::vector<int>::push_back, 1)
.
However, doing so doesn't exactly work, and debugging it is a bit of a nightmare. I just wondered whether anyone could give it a look over and help me get the correct functionality. I hope my code above has shown enough of my intent with the class.
I'm also a little unclear on how you would form the function pointer to std::vector<T>::push_back
given it takes an allocator as a second template argument.
Thanks.
CodePudding user response:
What about two value()
methods?
A const
one, only for reading
T const & read_value() const
{
m_updated = false;
return m_data;
}
and a second one, to modify the content
T & write_value()
{
m_updated = true;
return m_data;
}
CodePudding user response:
I have found a solution! (I think!)
So the first problem is that given vector<T>::push_back
is overloaded, when we pass the function point to call
we must cast it to the exact function pointer type we want i.e.
void (std::vector<int>::*f_ptr)(const int&) = &std::vector<int>::push_back;
and then we can implement the call()
function as follows:
template<typename Ret, typename... Args, typename... UArgs>
auto call(Ret (T::*func)(Args...), UArgs... args)
-> std::invoke_result_t<Ret(T::*)(Args...), T, UArgs...>
{
m_updated = true;
return std::invoke(func, m_data, args...);
}
However, this then has the problem that if T
of the original class is not a class itself, say T = int
then T::*
leads to a compilation error. If anyone has thoughts on how to resolve this, feel free to suggest some. SFINAE?
Edit: found a solution - template the function pointer on U
// if you want to update the value via a class method of T
template<typename U, typename Ret, typename... Args, typename... UArgs,
std::enable_if<!std::is_integral<U>::value, bool>::type = true
>
auto call(Ret (U::*func)(Args...), UArgs... args)
-> std::invoke_result_t<Ret(U::*)(Args...), U, UArgs...>
{
return std::invoke(func, m_data, args...);
}
And then an improved version using concepts:
template<typename U, typename Ret, typename... Args, typename... UArgs>
requires(std::same_as<T, U> && !std::is_integral_v<U>)
auto call(Ret (U::*func)(Args...), UArgs... args)
-> std::invoke_result_t<Ret(U::*)(Args...), U, UArgs...>
{
return std::invoke(func, m_data, args...);
}