4

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;
    }
};
Curious
  • 20,870
  • 8
  • 61
  • 146
  • That looks to me a bit like you'd be trying to simulate C++ using C. C++ has other, much better means of achieving what you want to do (implementing a generic wrapper for a string, an int, or a float) like inheritance and templates. – tofro Feb 26 '16 at 10:29

1 Answers1

10

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;
}
Simple
  • 13,992
  • 2
  • 47
  • 47
  • Could you edit the file I have above to do that? I think I already did do that... – Curious Feb 26 '16 at 09:53
  • I think I did provide a placement new construct in my constructor for the struct... – Curious Feb 26 '16 at 09:56
  • @Curious - try asking about the what you don't **understand** in Simple's answer – Elemental Feb 26 '16 at 09:56
  • Sorry. I just meant to say that I did provide a constructor for my struct that does the placement new. What else should I do? – Curious Feb 26 '16 at 10:02
  • Wait I really do not understand. Why would this answer ask me to do something I already had in my post? – Curious Feb 26 '16 at 10:08
  • 1
    @Curious see edit. Your original code doesn't have a constructor for the `union`. – Simple Feb 26 '16 at 10:10
  • Wait even when I added it there was still an error. Could you explain why this is the case? – Curious Feb 26 '16 at 10:13
  • 1
    @Curious i've changed the code sample, but both versions have worked for me. Show the errors my sample is giving you. – Simple Feb 26 '16 at 10:18
  • So I have the following code and it still isnt working. I will post the updated code in my question above. – Curious Feb 26 '16 at 21:15
  • @Curious : Your updated code still has no copy constructor or destructor for the union... – ildjarn Feb 26 '16 at 23:55
  • I understand my problem! Thank you for your answer. I will accept and upvote all your comments :) – Curious Feb 27 '16 at 03:36
  • When you construct a TU (tagged union) with std::string you call the the inner u's copy constructor and it std::move's the constructor argument. Why is it moved instead of copied? – Zebrafish Dec 27 '20 at 11:20