I have something like this:
template <typename T>
requires ::std::movable<T> || ::std::copyable<T> || ::std::is_void_v<T>
class ValueWrapper
{
// various functions to do stuff with value
};
class Value {
public:
Value(Value const &) = delete;
Value &operator =(Value const &) = delete;
Value(Value &&other) noexcept : x_{other.x_} { other.x_ = -1; }
Value &operator =(Value &&other) {
Value tmp{::std::move(other)};
x_ = tmp.x_;
tmp.x_ = -1;
return *this;
}
ValueWrapper<Value> do_something() const; // Generates error related to incomplete type. :-(
private:
int x_;
};
It, of course, doesn't work because at the time the compiler sees the do_something declaration, Value is an incomplete type, and it's not possible to test an incompete type for being movable or copyable, and it isn't void.
How should I design around this?
I could change ValueWrapper so the individual member functions required things instead of having the class require something of the template parameter. But, this seems obtuse, since the intent of the class is to wrap movable or copyable things. I could move do_something out of the Value class and make it a free function. But this seems overly constraining and may not be a sensible thing to do for the methods of any Value class.
Are there any other design choices here that don't have these shortcomings?
In the particular, non-abstracted case that currently concerns me, ValueWrapper happens to be something like Boost expected, and so is being used to signal either an error or value return.
Edit: My favorite answer so far involves using auto, and requires that the function definition appear inline in order to work. If you want the function definition to not be inline, you can do this bunch of gymnastics. But, it's really weird.
#include <concepts>
#include <utility>
template <typename T>
requires ::std::movable<T> || ::std::copyable<T> || ::std::is_void_v<T>
class ValueWrapper
{
// various functions to do stuff with value
};
namespace priv_ {
// We can forward declare a class without its member functions.
// But we can't forward declare a function without being able to
// fully name all of its types.
class Silly;
}
class Value {
public:
Value() : x_{-1} { }
Value(Value const &) = delete;
Value &operator =(Value const &) = delete;
Value(Value &&other) noexcept : x_{other.x_} { other.x_ = -1; }
Value &operator =(Value &&other) {
Value tmp{::std::move(other)};
x_ = tmp.x_;
tmp.x_ = -1;
return *this;
}
// inline here is basically documenting that we intend to give an
// inline definition later. We might want to say what the actual
// return type is too.
auto inline do_something() const;
private:
int x_;
// And we can friend a forward declared class so all of its member
// functions are basically member functions of this class (and
// hence have unrestricted access to all member functions and
// variables).
friend class priv_::Silly;
};
namespace priv_ {
class Silly {
public:
// And finally, now that the Value type is 'complete', we can
// use it as a parameter to the `ValueWrapper` template type.
static ValueWrapper<Value> p_do_something(Value const &v);
};
}
auto inline Value::do_something() const
{
// And now that the declaration for `p_do_something` has been
// seen, we can call it.
return priv_::Silly::p_do_something(*this);
}
ValueWrapper<Value> foo()
{
Value v;
return v.do_something();
}