Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Tagged union C++

I am trying to compile the following tagged union in a c++ file and I am running into issues. Could someone explain what I am missing or what I have to change to get the following to work? I have tried looking this up online and unfortunately have gotten nowhere...

#include <string>
using std::string;
#include <iostream>
using std::cout;
using std::endl;
#include <new>

const int TU_STRING = 0;
const int TU_INT = 1;
const int TU_FLOAT = 2;

struct TU {
   int type;
   union {
     int i;
     float f;
     std::string s;
   } u;

   TU(const TU& tu) : type(tu.type) {
     switch (tu.type) {
       case TU_STRING: new(&u.s)(tu.u.s); break;
       case TU_INT:    u.i = tu.u.i;      break;
       case TU_FLOAT:  u.f = tu.u.f;      break;
     }
   }
   ~TU() {
     if (tu.type == TU_STRING)
       u.s.~string();
   }
};

int main() {
    TU tu;

    return 0;
}

I am compiling with clang using the following command

g++ -std=c++14 some.cpp

and I am getting lots of compiling errors

some.cpp:18:4: error: call to implicitly-deleted default constructor of 'union (anonymous union at some.cpp:12:4)'
   TU(const TU& tu) : type(tu.type) {
   ^
some.cpp:15:18: note: default constructor of '' is implicitly deleted because variant field 's' has a non-trivial default constructor
     std::string s;
                 ^
some.cpp:18:4: error: attempt to use a deleted function
   TU(const TU& tu) : type(tu.type) {
   ^
some.cpp:15:18: note: destructor of '' is implicitly deleted because variant field 's' has a non-trivial destructor
     std::string s;
                 ^
some.cpp:20:13: error: use of undeclared identifier 'TU_STRING'
       case TU_STRING: new(&u.s)(tu.u.s); break;
            ^
some.cpp:20:34: error: unknown type name 'tu'
       case TU_STRING: new(&u.s)(tu.u.s); break;
                                 ^
some.cpp:20:36: error: expected ')'
       case TU_STRING: new(&u.s)(tu.u.s); break;
                                   ^
some.cpp:20:33: note: to match this '('
       case TU_STRING: new(&u.s)(tu.u.s); break;
                                ^
some.cpp:21:13: error: use of undeclared identifier 'TU_INT'
       case TU_INT:    u.i = tu.u.i;      break;
            ^
some.cpp:22:13: error: use of undeclared identifier 'TU_FLOAT'
       case TU_FLOAT:  u.f = tu.u.f;      break;
            ^
some.cpp:26:10: error: use of undeclared identifier 'tu'
     if (tu.type == TU_STRING)
         ^
some.cpp:26:21: error: use of undeclared identifier 'TU_STRING'
     if (tu.type == TU_STRING)
                    ^
some.cpp:25:4: error: attempt to use a deleted function
   ~TU() {
   ^
some.cpp:15:18: note: destructor of '' is implicitly deleted because variant field 's' has a non-trivial destructor
     std::string s;
                 ^
some.cpp:32:8: error: no matching constructor for initialization of 'TU'
    TU tu;
       ^
some.cpp:18:4: note: candidate constructor not viable: requires single argument 'tu', but no arguments were provided
   TU(const TU& tu) : type(tu.type) {
   ^
11 errors generated.

Thanks!

EDIT: Updated code which still doesnt work

struct VariantType {

    enum class Tag {INTEGER, STRING};
    Tag tag;
    union TypeUnion {
        string inner_string;
        int inner_int;

        TypeUnion(const std::string& str) {
            new(&inner_string) string(str);
        }
        TypeUnion(int inner_int_in) {
            inner_int = inner_int_in;
        }
        TypeUnion(std::string other) : inner_string{std::move(other)} {}
    } type_union;

    VariantType(const std::string& str) : tag{Tag::STRING} {
        new(&this->type_union.inner_string) string(str);
    }
    VariantType(int inner_int_in) : tag{Tag::INTEGER} {
        this->type_union.inner_int = inner_int_in;
    }
};
like image 846
Curious Avatar asked Dec 24 '22 08:12

Curious


1 Answers

std::string has a non-trivial constructor, so you need to write a constructor for the union that performs a placement new at s's location.


Here is a version of your code that adds constructors to the union. I don't think this is the best solution but it demonstrates what you need to do:

#include <iostream>
#include <new>
#include <string>
#include <utility>

const int TU_STRING = 0;
const int TU_INT = 1;
const int TU_FLOAT = 2;

struct TU {
    union my_union {
        struct i_type { int type; int i; } i;
        struct f_type { int type; float f; } f;
        struct s_type { int type; std::string s; } s;

        my_union(int i) : i{TU_INT, i} {}
        my_union(float f) : f{TU_FLOAT, f} {}
        my_union(std::string s) : s{TU_STRING, std::move(s)} {}
        my_union(my_union const& other) {
            // This is safe.
            switch (other.i.type) {
            case TU_INT:    ::new(&i) auto(other.i); break;
            case TU_FLOAT:  ::new(&f) auto(other.f); break;
            case TU_STRING: ::new(&s) auto(other.s); break;
            }
        }
        ~my_union() {
            // This is safe.
            if (TU_STRING == s.type) {
                s.~s_type();
            }
        }
    } u;

    TU(int i) : u(i) {}
    TU(float f) : u(f) {}
    TU(std::string s) : u(std::move(s)) {}
    TU(TU const&) = default;
    ~TU() = default;
};

int main() {
    TU tu("hello");
    std::cout << tu.u.s.s << '\n';
    return 0;
}
like image 164
Simple Avatar answered Dec 28 '22 08:12

Simple