I'm trying to write a wrapper class that conditionally disables four special member functions (copy construct, move construct, copy assignment and move assignment), below is a quick draft I used for testing purposes:
enum class special_member : uint8_t {
copy_ctor, move_ctor,
copy_asgn, move_asgn
};
template<typename MemberType, special_member...>
struct _disabled_wrapper {
public:
constexpr _disabled_wrapper(MemberType type) : _type(type) {}
public:
constexpr MemberType& unwrapped() { return _type; }
constexpr const MemberType& unwrapped() const { return _type; }
private:
MemberType _type;
};
template<typename MemberType, special_member ... Disables>
struct _disabled_wrapper<MemberType, special_member::copy_ctor, Disables...>
{
private:
using _parent_t = _disabled_wrapper<MemberType, Disables...>;
public:
constexpr _disabled_wrapper(const _parent_t& parent) : _parent(parent) {}
constexpr _disabled_wrapper(const _disabled_wrapper&) = delete;
constexpr _disabled_wrapper(_disabled_wrapper&&) = default;
constexpr _disabled_wrapper& operator=(const _disabled_wrapper&) = default;
constexpr _disabled_wrapper& operator=(_disabled_wrapper&&) = default;
public:
constexpr MemberType& unwrapped() { return _parent.unwrapped(); }
constexpr const MemberType& unwrapped() const { return _parent.unwrapped(); }
private:
_parent_t _parent;
};
template<typename MemberType, special_member ... Disables>
struct _disabled_wrapper<MemberType, special_member::move_ctor, Disables...>
{
public: //Todo: Make private post fix.
using _parent_t = _disabled_wrapper<MemberType, Disables...>;
public:
constexpr _disabled_wrapper(const _parent_t& parent) : _parent(parent) {}
constexpr _disabled_wrapper(const _disabled_wrapper&) = default;
constexpr _disabled_wrapper(_disabled_wrapper&&) = delete;
constexpr _disabled_wrapper& operator=(const _disabled_wrapper&) = default;
constexpr _disabled_wrapper& operator=(_disabled_wrapper&&) = default;
public:
constexpr MemberType& unwrapped() { return _parent.unwrapped(); }
constexpr const MemberType& unwrapped() const { return _parent.unwrapped(); }
private:
_parent_t _parent;
};
template<typename MemberType, special_member ... Disables>
struct _disabled_wrapper<MemberType, special_member::copy_asgn, Disables...>
{
private:
using _parent_t = _disabled_wrapper<MemberType, Disables...>;
public:
constexpr _disabled_wrapper(const _parent_t& parent) : _parent(parent) {}
constexpr _disabled_wrapper(const _disabled_wrapper&) = default;
constexpr _disabled_wrapper(_disabled_wrapper&&) = default;
constexpr _disabled_wrapper& operator=(const _disabled_wrapper&) = delete;
constexpr _disabled_wrapper& operator=(_disabled_wrapper&&) = default;
public:
constexpr MemberType& unwrapped() { return _parent.unwrapped(); }
constexpr const MemberType& unwrapped() const { return _parent.unwrapped(); }
private:
_parent_t _parent;
};
template<typename MemberType, special_member ... Disables>
struct _disabled_wrapper<MemberType, special_member::move_asgn, Disables...>
{
private:
using _parent_t = _disabled_wrapper<MemberType, Disables...>;
public:
constexpr _disabled_wrapper(const _parent_t& parent) : _parent(parent) {}
constexpr _disabled_wrapper(const _disabled_wrapper&) = default;
constexpr _disabled_wrapper(_disabled_wrapper&&) = default;
constexpr _disabled_wrapper& operator=(const _disabled_wrapper&) = default;
constexpr _disabled_wrapper& operator=(_disabled_wrapper&&) = delete;
public:
constexpr MemberType& unwrapped() { return _parent.unwrapped(); }
constexpr const MemberType& unwrapped() const { return _parent.unwrapped(); }
private:
_parent_t _parent;
};
The above code functions correctly, except for three cases (<move_ctor, move_asgn>
, <copy_ctor, move_asgn>
and <copy_ctor, move_ctor, move_asgn>
). Specifically, the assertion in the code below fails, which leads me to believe that for some reason the defaulted move assignment operator is not deleted, in the move_ctor
partial specialisation of _disabled_wrapper
, even though its non-static member _parent
has its move assignment deleted. cppreference.com states:
The implicitly-declared or defaulted move assignment operator for class T is defined as deleted if any of the following is true:
- T has a non-static data member that is const;
- T has a non-static data member of a reference type;
- T has a non-static data member that cannot be move-assigned (has deleted, inaccessible, or ambiguous move assignment operator);
- T has direct or virtual base class that cannot be move-assigned (has deleted, inaccessible, or ambiguous move assignment operator).
A deleted implicitly-declared move assignment operator is ignored by overload resolution.
Which makes me believe that, according to the standard, the move assignment operator should indeed be deleted (bullet point three). What am I missing and or, if possible, how do I make the wrapper class work as intended without manually typing all possible specialisations? Thank you.
int main() {
using type = _disabled_wrapper<int, special_member::move_ctor, special_member::move_asgn>;
//Passes, hence member variable has its move_assignment deleted as intended.
static_assert(std::is_move_assignable_v<type::_parent_t> == false);
//Fails here! Move assignment defined?
static_assert(std::is_move_assignable_v<type> == false);
return 0;
}
CodePudding user response:
The last statement in your quote "A deleted implicitly-declared move assignment operator is ignored by overload resolution." is more precisely:
A defaulted move assignment operator that is defined as deleted is ignored by overload resolution.
type
has a defaulted move assignment operator which is defined as deleted because it is deleted in a base class.
Therefore, this operator is ignored by overload resolution.
Therefore, assignment from an rvalue selects copy assignment.
Therefore, type
is move assignable because it is copy assignable.