Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the use of const union members? Aren't they quite pointless?

Tags:

c++

unions

In the comments to this answer, Koushik raised a very valid point.

Take the following:

union U
{
    int x;
    const T y;
};

(I choose T such that there is no common initial sequence of layout compatibility here, meaning only one member may be active at any given time per [C++11: 9.5/1].)

Since only one member may be "active" at any one time (made active by writing to it), and y cannot be written to after initialisation, isn't this rather pointless? I mean, y can only be read from until the first time x is written to, and at that only if y was the initialised member.

Is there some use case I'm missing? Or is this indeed a pretty pointless confluence of language features?

(This has been mentioned before)

like image 895
Lightness Races in Orbit Avatar asked Jan 03 '14 14:01

Lightness Races in Orbit


2 Answers

Here's a contrived example of a reference-semantics type where you'd only want to grant const access to. The union is used in a variant-like data type returned from a "type-erasing" function.

#include <memory>

template<class T>
struct reference_semantics
{
public:
    reference_semantics(T* p ) : m(p) {}

    int observe() const { return *m; }
    void change(T p) { *m = p; }

private:
    T* m;
};

struct variant
{
    enum T { INT, DOUBLE } type;

    union U
    {
        reference_semantics<int> const i;
        reference_semantics<double> const d;

        U(int* p) : i(p) {}
        U(double* p) : d(p) {}
    } u;
};

#include <iostream>
std::ostream& operator<<(std::ostream& o, variant const& v)
{
    switch(v.type)
    {
        case variant::INT:
            return o << "INT: "<<v.u.i.observe();
        case variant::DOUBLE:
            return o << "DOUBLE: "<<v.u.d.observe();
    }
}

#include <string>

variant type_erased_access(std::string name)
{
    // imagine accesses to a map or so

    static double dval = 42.21;
    static int ival = 1729;

    if(name == "Lightness") return { variant::DOUBLE, &dval };
    else return { variant::INT, &ival };
}

int main()
{
    variant v0( type_erased_access("Lightness") );
    std::cout << v0 << "\n";
    variant v1( type_erased_access("Darkness") );
    std::cout << v1 << "\n";
}

Imagine now that instead of int and double, much larger data types are used, and that the reference_semantics data type actually provides more functionality than just returning the value.

It might even be possible that you want to return a reference_semantics<some_type> const for some arguments, but a plain int for others. In that case, your union might even have const and non-const members.

like image 81
dyp Avatar answered Sep 23 '22 04:09

dyp


It does have uses:

1) For offering a const_cast-like technique. In a sense, x = const_cast<...>(y).

2) When dealing with templates, sometimes you need a const version of a data type so you match other parameter types.

(I've seen (1) used when programming against legacy interfaces).

like image 33
Bathsheba Avatar answered Sep 19 '22 04:09

Bathsheba