C++ "Choice" Union
Asked Answered
I

4

6

Not sure if there is a term for this, "choice" seems to work. I'm working in C++, and I have a bunch of unions I need to create where the union represents a choice of one of the members of the union. The current "choice" is tracked and is always available. I am currently coding these "unions" manually, but I'm wondering whether there is any neat trick for doing this sort of thing (semi-)automatically.

I ran into the union limitation of not having assignment operator overloads or non-trival constructors or copy constructors on my first bout of trying to implement this, but realized that because I'm actually tracking the current "choice", there is very defined behavior to take under almost every situation.

Here is what I'm doing right now, (for only two choices, could be up to 10 or 15) and it's quite a significant amount of code nearly all of which is just boilerplate. Also, if anyone has any comments on whether or not what I have below is even valid that would be awesome, still picking up some of the craziness of C++...

struct MyChoice
{
    struct Choice1
    {
        int a;
        char* b;
    };

    struct Choice2
    {
        bool c;
        double d;
    };

    enum Choice
    {
        Choice_Choice1,
        Choice_Choice2
    } choice;

    char _value[max(sizeof(Choice1),sizeof(Choice2))]; // could be private
    Choice1& choice1()
    {
        if(choice == Choice_Choice2)
        {
            (*(Choice2*)_value)->~Choice2();
            (*(Choice1*)_value) = Choice1();
            choice = Choice_Choice1;
        }
        return *(Choice1*)_value;
    }
    Choice2& choice2()
    {
        if(choice == Choice_Choice1)
        {
             (*(Choice1*)_value)->~Choice1();
             (*(Choice2*)_value) = Choice2();
             choice = Choice_Choice2; 
        }
        return *(Choice2*)_value;
    }
    MyChoice()
    {
       _choice = Choice_Choice1;
       (*(Choice1)_value) = Choice1();
    }
    MyChoice(const MyChoice& other)
    {
       this->_choice = other.choice;
       if(this->_choice == Choice_Choice1)
          (*(Choice1*)_value) = other.choice1();
       else
          (*(Choice2*)_value) = other.choice2();
    }
    ~MyChoice()
    {
        if(_choice == Choice_Choice1)
            (*(Choice1)_value)->~Choice1();
        else
            (*(Choice2)_value)->~Choice2();
    }
};

Thanks for your help SO

Illuminate answered 21/7, 2010 at 21:26 Comment(2)
That's called a variant record in Pascal. C/C++ doesn't have special syntax for them probably for ease of implementation, and because unions provide a way to achieve a similar result, so why bother.Sucking
Thanks for the info + history :)Illuminate
Q
15

Try looking at boost::any and boost::variant. The first one enables you to insert any type in a boost::any variable, tracking its type. It's more a "check-at-runtime" type. The second one force you to define all the types to be inserted (ie boost::variant < Choice1, Choice2, ... > ), but enforce more type-checking at compile time.

Both are used to store object of different types, for example to have heterogeneous containes (std::vector can handle std::string or int for example).

Quagga answered 21/7, 2010 at 21:31 Comment(2)
boost:variant seems close, would there be any way for me to distinguish between two distinct choices that have the same type? Suppose choice2 and choice3 are both integers, but represent different things, any way to handle this with variant?Illuminate
You can define your own universal template type wrapper for this, e.g.: template<int Discriminant, typename Value> struct distinct_type { Value value; }. Then you can create distinct wrappers as needed: distinct_type<0, int>, distinct_type<1, int> etc. Better yet, define an enum and use its constants for Discriminant.Foolery
I
6

More generally this is a 'discriminated union' or tagged union. As mentioned boost::variant or boost::any are both implementations of this strategy.

Interbedded answered 21/7, 2010 at 21:33 Comment(1)
thanks for the proper name... wasn't quite sure what to call itIlluminate
A
4

With C++ 17, there's a std::variant type directly provided in the standard library.

Here's a small extract of the example source code from cppreference:

#include <variant>
#include <string>
#include <cassert>

using namespace std::literals;

int main()
{
    std::variant<int, float> v, w;
    v = 12; // v contains int
    int i = std::get<int>(v);
    w = std::get<int>(v);
    w = std::get<0>(v); // same effect as the previous line
    w = v; // same effect as the previous line

//  std::get<double>(v); // error: no double in [int, float]
//  std::get<3>(v);      // error: valid index values are 0 and 1
}
Attraction answered 28/8, 2018 at 12:40 Comment(0)
L
3

Even if you are like me and generally prefer variants to inheritance (I'm an ML kind of guy), inheritance is the C++ way to do it.

Instead of using an object of boost::variant<Apple, Pear, Banana>, use a smart pointer to an object of Fruit. Inheritance has the advantage of being open -- you can always add more types of Fruit. Virtual methods are usually much cleaner than switches or if statements. Give inheritance a chance; you'll learn to like it.

Logarithmic answered 21/7, 2010 at 21:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.