1

I have a problem with the Clustering class, the union within it is causing a problem.

I have tried several things and still nothing, checked the operators, but wasn't able to find the error.

This was built originally with Visual Studio 2008, and it compiles just fine there.
But I must build it with Visual Studio 2019 and encountered this problem during compilation:

error C2280: 'Clustering<T>::NewType::~NewType(void)': attempting to reference a deleted function
error C2280:         with
error C2280:         [
error C2280:             T=ConsortiaRelationElem
error C2280:         ]

I imagine this error is due to the implementation of the c++ 11 parameters.

Here's the definition for the Clustering class:

template <typename T> 
class Clustering
{
    friend CPlayer;
public: 
    union NewType
    {  
        struct  
        {
            T _Ttype;
        };
        CPlayer* m_player;
    };

    NewType m_Data;
public:
    Clustering(){};
    Clustering(const T& _Ttype)
    { 
        m_Data.m_player = NULL;
        memcpy( &m_Data._Ttype, &_Ttype, sizeof(T) );
    };
    Clustering& operator=(const T& _Ttype)
    {
        m_Data.m_player = NULL;
        memcpy(&m_Data._Ttype, &_Ttype, sizeof(T));
    };
    ~Clustering() { };
};

This are all the errors i currently get while compiling:

warning C4624: 'Clustering<T>::NewType': destructor was implicitly defined as deleted
warning C4624:         with
warning C4624:         [
warning C4624:             T=ConsortiaRelationElem
warning C4624:         ]

error C2280: 'Clustering<T>::NewType::~NewType(void)': attempting to reference a deleted function
error C2280:         with
error C2280:         [
error C2280:             T=ConsortiaRelationElem
error C2280:         ]
message : 'Clustering<T>::NewType::~NewType(void)': function was implicitly deleted because 'Clustering<T>::NewType' has a variant data member 'Clustering<T>::NewType::_Ttype' with a non-trivial destructor
Turtlefight
  • 9,420
  • 2
  • 23
  • 40
  • 1
    Welcome to Stack Overflow. Please read [the help pages](http://stackoverflow.com/help), take the SO [tour], read [ask], as well as [this question checklist](https://codeblog.jonskeet.uk/2012/11/24/stack-overflow-question-checklist/). Lastly please learn how to [edit] your questions to improve them, for example by showing us a proper [mre] and a full and complete copy-paste of the errors (copy-paste from the build-log). – Some programmer dude Dec 08 '21 at 16:24
  • 3
    related/dupe: https://stackoverflow.com/questions/40106941/is-a-union-members-destructor-called – NathanOliver Dec 08 '21 at 16:26
  • _"...If a union contains a non-static data member with a non-trivial special member function (copy/move constructor, copy/move assignment, or destructor), that function is deleted by default in the union and needs to be defined explicitly by the programmer..."_ https://en.cppreference.com/w/cpp/language/union – Richard Critten Dec 08 '21 at 16:36
  • 2
    *the union is causing a problem* -- I guess it may be time to use `std::variant` instead of `union`. – PaulMcKenzie Dec 08 '21 at 16:37
  • Before adding Clustering & operator = (const T & _Ttype) as a solution to a problem, it told me that (error C2280: 'Clustering & Clustering :: operator = (const Clustering &)': attempting to reference a deleted function) [due: m_MemberNameMap [rElements.strName] = rElements; ], after adding the operator, the error happened to be another one that kept getting me if I hid the line – Alejandro Rojas Dec 08 '21 at 16:54
  • 1
    `operator=` does not return anything. Enable your compiler warnings. Fix the warnings. – Eljay Dec 08 '21 at 17:37
  • 1
    `ConsortiaRelationElem` is non-trivial, and cannot be `std::memcpy`'d. – Eljay Dec 08 '21 at 17:43
  • @AlejandroRojas I made a few edits to your question to make it easier to understand. If you don't like the way i've rewritten it please roll it back to the previous version. – Turtlefight Dec 08 '21 at 19:36

1 Answers1

0

1. non-trivial union members

One of the problems in your code is the union.

unions only implicitly define their special member functions if their members have trivial implimentations for them.
-> Once you add a member to a union that implements a special member function you'll have to manually implement that one for your union.

From the c++ standard:

At most one non-static data member of a union may have a brace-or-equal-initializer. [Note: if any non-static data member of a union has a non-trivial default constructor (12.1), copy constructor (12.8), move constructor (12.8), copy assignment operator (12.8), move assignment operator (12.8), or destructor (12.4), the corresponding member function of the union must be user-provided or it will be implicitly deleted (8.4.3) for the union. — end note ]

Example:

// only trivial members: the compiler will provide all functions for you
union A {
  int i;
  float f;
  double d;

  // member functions provided by your compiler
  A() = default;
  A(const A&) = default;
  A(A&&) = default;
  A& operator=(const A&) = default;
  A& operator=(A&&) = default;
  ~A() = default; 
};

// std::string & std::vector have non-trivial constructors, assignment operators and destructors
// so the member functions will be deleted.
union B {
  int i;
  std::string s;
  std::vector<int> v;

  // member functions provided by your compiler
  B() = delete;
  B(const B&) = delete;
  B(B&&) = delete;
  B& operator=(const B&) = delete;
  B& operator=(B&&) = delete;
  ~B() = delete; 
};

godbolt example with your code

The reasoning behind this is that there is no way the compiler could actually generate those functions. For example the constructor of union B would need to know if it needs to call the constructor of std::string, std::vector or none at all - but that depends on which member you initialize which the compiler doesn't know yet.

Same thing for the destructor of union B - which destructor should it call? std::string::~string() ? or std::vector<int>::~vector()? or none at all in the int case? The union doesn't know which member is the active one so there's no way to provide a sensible destructor.

So the compiler leaves the responsibility of actually implementing all those special functions up to you.

A very simple example of implementing a two-type generic union could look like this:


template<class T, class U>
struct variant {
private:
    enum {
        IS_NONE,
        IS_T,
        IS_U
    } activeMember;

    union {
        T t;
        U u;
        char dummy;
    };

public:
    variant() : activeMember(IS_NONE), dummy() {}
    variant(T const& _t): activeMember(IS_T), t(_t) {}
    variant(U const& _u): activeMember(IS_U), u(_u) {}
    variant(variant const& other): variant() { *this = other; }

    variant& operator=(variant const& other) {
        if(&other != this) {
            reset();
            activeMember = other.activeMember;
            if(other.activeMember == IS_T)
                new (&t) T(other.t);
            else if(other.activeMember == IS_U)
                new (&u) U(other.u);
        }
        return *this;
    }

    variant& operator=(T const& _t) {
        reset();
        activeMember = IS_T;
        new (&t) T(_t);
        return *this;
    }

    variant& operator=(U const& _u) {
        reset();
        activeMember = IS_U;
        new (&u) U(_u);
        return *this;
    }

    explicit operator T const&() {
        if(activeMember != IS_T) throw std::domain_error("variant is not T!");
        return t;
    }

    explicit operator U const&() {
        if(activeMember != IS_U) throw std::domain_error("variant is not U!");
        return u;
    }

    explicit operator bool() {
        return activeMember != IS_NONE;
    }

    ~variant() { reset(); }

    void reset() {
        if(activeMember == IS_T)
            t.~T();
        else if(activeMember == IS_U)
            u.~U();
        activeMember = IS_NONE;
    }
};

godbolt example

Which could be used like this:

variant<int, std::string> v = 12;
v = "MIAU";

Notice that we need to initialize one of the union members in the constructors, as well as call their respective destructors as needed.
If we want to switch the active member after construction we also need to destruct the previosly active union member first, then use placement-new to construct the new value.

1.1 using std::variant

If you can use C++17 i'd recommend using std::variant instead of the union, since that basically does the exact same thing as the variant class i posted above and more.


2. memcpy of non-trival types

You use memcpy( &m_Data._Ttype, &_Ttype, sizeof(T) ); both in the constructor and assignment operator of your class.

You are only allowed to copy objects with memcpy if they are trivially copyable, i.e.:

static_assert(std::is_trivially_copyable<T>::value, "T is not trivially copyable!");

Given the compile error you got your T is most likely not trivially copyable, so using memcpy in this case leads to undefined behaviour.

You can instead use placement new to call the copy- or move constructor of T, e.g.:

template<class T>
Clustering<T>& Clustering<T>::operator=(T const& t)
{
    new (&m_Data._Ttype) T(t);
    return *this;
};

Also don't forget to call the destructor for your T (if it's not trivially destructable) in your ~Clustering() destructor (otherwise you leak your T!):

template<class T>
Clustering<T>::~Clustering() {
    if(/* union actually contains T */)
        m_Data._Ttype.~T();
}

  1. Full working example

In your example you could write Clustering like this:

template <typename T> 
class Clustering
{
    friend CPlayer;
public: 
    union
    {
        T _Ttype;
        CPlayer* m_player;
    };
    bool containsT;
public:
    Clustering() : containsT(false), m_player(nullptr) {};
    Clustering(T const& t) : containsT(true), _Ttype(t) {};
    
    Clustering& operator=(T const& t)
    {
        // cleanup old T if needed
        if(containsT)
          _Ttype.~T();

        containsT = true;
        new (&_Ttype) T(t);

        return *this;
    };


    ~Clustering() {
        if(containsT)
            _Ttype.~T();
    };
};

working godbolt example

(notice that we can avoid cleaning up m_player, since pointers are trivial.)

Turtlefight
  • 9,420
  • 2
  • 23
  • 40
  • I have not been able to solve this problem, I have not had much time to address it as I wanted, but I wanted to see if I had a little time available to review the code, I am giving you a link so that you can analyze it, since everything I read or try to doing does not give me a solution and if I can make it compatible with c ++ 17, I only have to accommodate a few small things about the byte – Alejandro Rojas Dec 19 '21 at 03:34
  • [LINK](https://mega.nz/file/APAxkQJT#w_iVWXezgEglTE_VzdF5Z-snGb9LTH2KZyARGGU6wXE ) – Alejandro Rojas Dec 19 '21 at 03:44
  • binary '=': no operator found which takes a right-hand operand of type 'const std::pair' (or there is no acceptable conversion) – Alejandro Rojas Dec 19 '21 at 17:44
  • error C2679: with error C2679: [ error C2679: _Kty=std::string, error C2679: _Ty=Clusteringerror C2679: ] – Alejandro Rojas Dec 19 '21 at 17:50