8

Suppose I have a struct that contains a union with const members, like so:

struct S
{
  // Members

  const enum { NUM, STR } type;

  union
  {
    const int a;
    const std::string s;
  };

  // Constructors

  S(int t_a) : type(NUM), a(t_a);

  S(const std::string & t_s) : type(STR), s(t_s);

};

So far, so good. But now say I want to write a copy-constructor for this type.

It doesn't seem like this involves doing anything nefarious, but since I need to initialize the const members in member initializers I don't see how to do this based on logic that depends on the type member.

Questions:

  • Is it possible to write this constructor?

  • If not, is this essentially a syntactic oversight, or is there some fundamental reason that the language can't support such a thing?

Daniel McLaury
  • 4,047
  • 1
  • 15
  • 37
  • 3
    Might want to try a `const std::variant` instead. – François Andrieux Feb 12 '19 at 20:23
  • 1
    If you want to use `std::string` in a union you really need to use a tagged union. A non trivial type in a union deleted all of the unions special member functions and it isn't trivial to re-implement them correctly. – NathanOliver Feb 12 '19 at 20:23
  • 1
    @NathanOliver: I did tag the union; that's what the `type` member is for. (If you're just pointing out that I'll need to e.g. implement my own destructor, I understand that, but left it out of the code sample for brevity.) – Daniel McLaury Feb 12 '19 at 20:26
  • Oh, silly me. You are building a tagged union. `std::variant`'s source code should give you an example of how to do it, since it support const members. – NathanOliver Feb 12 '19 at 20:31
  • 2
    You should probably be able to use placement-new to initialize either `a` or `s` in the copy-constructor body without UB. Related: https://stackoverflow.com/questions/33058717/do-unrestricted-unions-require-placement-new-and-a-constructor-definition – Holt Feb 12 '19 at 20:46
  • @Holt: Can you elaborate? If I try to do that naively I get "invalid conversion from ‘const void*’ to ‘void*’" – Daniel McLaury Feb 12 '19 at 21:20

1 Answers1

2

Yes, it is possible to write copy constructor here. Actually it is already done inside std::variant implementation, which shall support const-types among others. So your class S can be replaced with

using S = std::variant<const int, const std::string>;

But if for dome reason you cannot use std::variant then copy-constructor can be written using std::construct_at function as follows:

#include <string>

struct S {
  const enum { NUM, STR } type;

  union {
    const int a;
    const std::string s;
  };

  S(int t_a) : type(NUM), a(t_a) {}
  S(const std::string & t_s) : type(STR), s(t_s) {}
  S(const S & rhs) : type(rhs.type) {
      if ( type == NUM ) std::construct_at( &a, rhs.a );
      if ( type == STR ) std::construct_at( &s, rhs.s );
  }
  ~S() {
      if ( type == STR ) s.~basic_string();
  }
};

int main() {
    S s(1);
    S u = s;

    S v("abc");
    S w = v;
}

Demo: https://gcc.godbolt.org/z/TPe8onhWs

Fedor
  • 17,146
  • 13
  • 40
  • 131