{%- set union_prefix = "%s.%s"|format(module_prefix, union.name) %}

{{ kythe_annotation(union_prefix) }}
class {{export_attribute}} {{union.name}} {
 public:
  using DataView = {{union.name}}DataView;
  using Data_ = internal::{{union.name}}_Data;
  using Tag = Data_::{{union.name}}_Tag;

  template <typename... Args>
  static {{union.name}}Ptr New(Args&&... args) {
    static_assert(
        sizeof...(args) < 0,
        "Do not use Union::New(); to create a union of a given subtype, use "
        "New<SubType>(), not New() followed by set_<sub_type>(). To represent "
        "an empty union, mark the field or parameter as nullable in the mojom "
        "definition.");
    return nullptr;
  }
{%  for field in union.fields %}
  // Construct an instance holding |{{field.name}}|.
  static {{union.name}}Ptr New{{field.name|under_to_camel}}(
      {{field.kind|cpp_wrapper_param_type_new}} value);
{%- endfor %}

  template <typename U>
  static {{union.name}}Ptr From(const U& u) {
    return mojo::TypeConverter<{{union.name}}Ptr, U>::Convert(u);
  }

  template <typename U>
  U To() const {
    return mojo::TypeConverter<U, {{union.name}}>::Convert(*this);
  }
  ~{{union.name}}();
{%  if not union|should_inline_union %}
  // Delete the copy constructor and copy assignment operators because `data_`
  // contains raw pointers that must not be copied.
  {{union.name}}(const {{union.name}}& other) = delete;
  {{union.name}}& operator=(const {{union.name}}& other) = delete;
{%- endif %}

  // Clone() is a template so it is only instantiated if it is used. Thus, the
  // bindings generator does not need to know whether Clone() or copy
  // constructor/assignment are available for members.
  template <typename UnionPtrType = {{union.name}}Ptr>
  {{union.name}}Ptr Clone() const;

  // Equals() is a template so it is only instantiated if it is used. Thus, the
  // bindings generator does not need to know whether Equals() or == operator
  // are available for members.
  template <typename T,
            typename std::enable_if<std::is_same<
                T, {{union.name}}>::value>::type* = nullptr>
  bool Equals(const T& other) const;

  template <typename T,
            typename std::enable_if<std::is_same<
                T, {{union.name}}>::value>::type* = nullptr>
  bool operator==(const T& rhs) const { return Equals(rhs); }

{%- if union|is_hashable %}
  size_t Hash(size_t seed) const;
{%- endif %}

  Tag which() const {
    return tag_;
  }
{%  for field in union.fields %}
  {{- kythe_annotation("%s.%s"|format(union_prefix, field.name)) }}
  bool is_{{field.name}}() const { return tag_ == Tag::k{{field.name|under_to_camel}}; }
{%- if field.kind|is_double_kind or field.kind|is_enum_kind or field.kind|is_float_kind or field.kind|is_integral_kind %}
  {{- kythe_annotation("%s.%s"|format(union_prefix, field.name)) }}
  {{field.kind|cpp_union_getter_return_type}} get_{{field.name}}() const {
    CHECK(tag_ == Tag::k{{field.name|under_to_camel}});
    return data_.{{field.name}};
  }
{%- else %}
  {{- kythe_annotation("%s.%s"|format(union_prefix, field.name)) }}
  const {{field.kind|cpp_union_getter_return_type}} get_{{field.name}}() const {
    CHECK(tag_ == Tag::k{{field.name|under_to_camel}});
    return data_.{{field.name}};
  }
  {{- kythe_annotation("%s.%s"|format(union_prefix, field.name)) }}
  {{field.kind|cpp_union_getter_return_type}} get_{{field.name}}() {
    CHECK(tag_ == Tag::k{{field.name|under_to_camel}});
    return data_.{{field.name}};
  }
{%- endif %}
  {{- kythe_annotation("%s.%s"|format(union_prefix, field.name)) }}
  void set_{{field.name}}({{field.kind|cpp_wrapper_param_type_new}} {{field.name}});
{%  endfor %}
  template <typename UserType>
  static mojo::Message SerializeAsMessage(UserType* input) {
    return mojo::internal::SerializeAsMessageImpl<
        {{union.name}}::DataView>(input);
  }

  template <typename UserType>
  static bool DeserializeFromMessage(mojo::Message input,
                                     UserType* output) {
    return mojo::internal::DeserializeImpl<{{union.name}}::DataView>(
        input, input.payload(), input.payload_num_bytes(), output, Validate);
  }

 private:
  template <typename T>
  friend class ::mojo::InlinedStructPtr;
  template <typename T>
  friend class ::mojo::StructPtr;

  union Union_ {
{%- for field in union.fields %}
    Union_(
        std::in_place_index_t<static_cast<size_t>(Tag::k{{field.name|under_to_camel}})>,
        {{field.kind|cpp_wrapper_param_type_new}} value);
{%- endfor %}
    // The contents of the union are explicitly destroyed by `DestroyActive()`.
    ~Union_() {}
{%  for field in union.fields %}
    {{field.kind|cpp_wrapper_type}} {{field.name}};
{%- endfor %}
  };
{%  for field in union.fields %}
  {{union.name}}(
      std::in_place_index_t<static_cast<size_t>(Tag::k{{field.name|under_to_camel}})>,
      {{field.kind|cpp_wrapper_param_type_new}} value);
{%- endfor %}

  static bool Validate(const void* data,
                       mojo::internal::ValidationContext* validation_context);

  void DestroyActive();
  Tag tag_;
  Union_ data_;
};
