Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use a union of two types

Tags:

c++

unions

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;
}
like image 899
cpp beginner Avatar asked May 04 '16 12:05

cpp beginner


People also ask

Can a variable have 2 types?

In TypeScript, a union type variable is a variable which can store multiple type of values (i.e. number, string etc). A union type allows us to define a variable with multiple types. The union type variables are defined using the pipe ( '|' ) symbol between the types.

What is union in typing?

TypeScript - UnionTypeScript allows us to use more than one data type for a variable or a function parameter. This is called union type.

Which of the following is called combining of type types?

An intersection type combines multiple types into one. This allows you to add together existing types to get a single type that has all the features you need.


3 Answers

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.

like image 97
Rotem Avatar answered Oct 18 '22 05:10

Rotem


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.

like image 2
Yakk - Adam Nevraumont Avatar answered Oct 18 '22 03:10

Yakk - Adam Nevraumont


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>();
like image 1
Andreas DM Avatar answered Oct 18 '22 04:10

Andreas DM