10

I am trying to make a vector that can hold string and int.

I've tried the code below, but I get the compilation error

error: use of deleted function 'my_union::~my_union()'

What am I doing wrong?

#include <iostream>
#include <vector>

using namespace std;

union my_union
{
    string str;
    int a;
};

int main() 
{
    vector<my_union> v;
    my_union u;         // error: use of deleted function 'my_union::~my_union()'
    u.str = "foo";
    v.push_back(u);
    return 0;
}
cpp beginner
  • 512
  • 6
  • 23
  • Reopened: C++ 11 greatly expanded what's allowed in a union, and `std::string`, among others, is now allowed. – Pete Becker May 04 '16 at 12:27
  • 1
    @PeteBecker I get that but isn't that covered in [this](http://stackoverflow.com/a/3521998/4342498) answer from the Q? – NathanOliver May 04 '16 at 12:30
  • 2
    How are you planning to 'know' which member of the union to read from the vector later? – M.M May 04 '16 at 12:33
  • @M.M That's a good question. There's only a small number of possible strings, so I was going to check the string member for equality with one of those, and if no match, it must be an int. Thinking about is, this sounds like a terrible, error-prone idea. – cpp beginner May 04 '16 at 12:36
  • @NathanOliver - that question and answer are from 2010, **before** the change that allowed this. A C++11 compiler **must** compile code that puts a `std::string` in a union, so the correct answer to **this** question is that the compiler is wrong. The linked answer, which talks about why doing this doesn't make any sense, is equally wrong. – Pete Becker May 04 '16 at 12:36
  • 1
    @cppbeginner That cannot work. The `int` overlaps the initial part of the `std::string` object. So if there actually was an `int` in there, the `std::string` representation would be *very* corrupt internally, and accessing it would likely give you very weird results or a crash. Formally, it's Undefined Behaviour to access an inactive member of a union. – Angew is no longer proud of SO May 04 '16 at 12:40
  • @Angew You're right. I didn't think it through. I need to completely change my approach. – cpp beginner May 04 '16 at 12:41
  • 3
    @cppbeginner You might want to look into [`boost::variant`](http://www.boost.org/doc/libs/1_60_0/doc/html/variant.html) – Angew is no longer proud of SO May 04 '16 at 12:42
  • @Angew Thanks, that's very helpful. That's what I'll do. – cpp beginner May 04 '16 at 12:51

3 Answers3

14

From here

If a union contains a non-static data member with a non-trivial special member function (default constructor, 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.

You must explicitly define a destructor for your union to replace the one automatically deleted for string.

Also note that this is only valid in c++11. In earlier versions you can not have a type with non-trivial special member functions inside a union at all.

From a practical point of view, this may still not be a great idea.

Rotem
  • 21,452
  • 6
  • 62
  • 109
  • 4
    "may ... not be a good idea" -- indeed. When you have types with non-trivial constructors or destructors you have to use placement new and explicit destructor calls to store an object of a different type in the union. – Pete Becker May 04 '16 at 12:32
  • 1
    @CodesInChaos A comment to this question alludes to [boost::variant](http://www.boost.org/doc/libs/1_60_0/doc/html/variant.html). I haven't used it myself but from the description on the webpage it sounds spot on, even giving exactly the situation in the question as an example. – Rotem May 04 '16 at 17:45
2

When you create a union with a class that isn't basically plain old data, in C++11 it lets you. But it goes and implicitly deletes most of the special member functions like the destructor.

union my_union
{
  string str;
  int a;
};

the practical problem is that at the point of destruction C++ doesn't know which of the above parts of the union are valid.

You can work around this by using a tagged union, and keeping track which is active, and manually doing the destruction in that case.

So we can get something like:

struct tagged_union {
  enum active {nothing, string, integer} which_active;
  template<active...As>
  using actives = std::integral_sequence<active, As...>
  using my_actives = actives<nothing, string, integer>;

  struct nothingness {};

  union my_union
  {
    nothingness nothing;
    std::string str;
    int a;
    ~my_union() {};
  } data;
  using my_tuple = std::tuple<nothingness, std::string, int>;

  template<active which>
  using get_type = std::tuple_element_t<(std::size_t)which, my_tuple>;

  template<class F>
  void operate_on(F&& f) {
    operate_on_internal(my_actives{}, std::forward<F>(f));
  }
  template<class T, class F>
  decltype(auto) operate_on_by_type(F&& f) {
    return std::forward<F>(f)(reinterpret_cast<T*>(&data));
  }
  // const versions go here
private:
  // a small magic switch:
  template<active...As, class F>      
  void operate_on_internal(actives<As...>, F&& f) {
    using ptr = void(*)(my_union*,std::decay_t<F>*);
    const ptr table[]={
      [](my_union* self, std::decay_t<F>* pf){
        std::forward<F>(*pf)(*(get_type<As>*)self);
      }...,
      nullptr
    };
    table[which](&data, std::address_of(f));
  } 
public:
  template<class...Args>
  tagged_union(Active w, Args&&...args) {
    operate_on([&](auto& t){
      using T = std::decay_t<decltype(t)>();
      ::new((void*)std::addressof(t)) T(std::forward<Args>(args)...);
      which = w;
    });
  }
  tagged_union():tagged_union(nothing){}

  ~tagged_union() {
    operate_on([](auto& t){
      using T = std::decay_t<decltype(t)>();
      t->~T();
      which=nothing;
      ::new((void*)std::addressof(t)) nothingness{}; // "leaks" but we don't care
    });
  }
};

which is basically a primitive sketch of how something like boost::variant works if written in C++11.

It involves some heavy mojo.

The above has not been compiled, but the design is sound. Some nominally C++14 compilers don't like doing a pack expand around a full lambda, however, which would require even more boilerplate.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
1

Before C++11 it was not allowed to use std::string in a union as quoted here:

Unions cannot contain a non-static data member with a non-trivial special member function (copy constructor, copy-assignment operator, or destructor).

And since C++11 you can use std::string in a union as already answered by @Rotem, you need to define a destructor explicitly for string or call the destructor explicitly

str.~basic_string<char>();
Community
  • 1
  • 1
Andreas DM
  • 10,685
  • 6
  • 35
  • 62