0

I'm stuck with c++17 for a project, so I don't have access to designated initializers. I have a bunch of union types that I want to avoid initializing this way (because it is annoying):

MyUnionType x;
x.value = value_set_for_all_union_members;

I want to instead have this

MyUnionType x(value_set_for_all_union_members); 

But I also want to avoid writing an implementation for each union I create. I know that all my union types are going to be of the following structure, each union is meant to actually represent bit a bit field, so I actually do want type pruning here, I know it is "UB" according to C++, but that is on the C++ committee, in C it is not undefined behavior, and thus all compilers that I care about will do what I want here.

union Example{
    integer_type value;
    custom_safe_bitfield_abstraction<...> a;
    custom_safe_bitfield_abstraction<...> b;
    ...
};

I thought, okay, I'll just inherit the constructor, and use CRTP to extract the appropriate integer_type. Of course I can't inherit on a union directly, so instead I opted for this strategy:

struct Example : Base<Example>{
    union{
        integer_type value;
        custom_safe_bitfield_abstraction<...> a;
        custom_safe_bitfield_abstraction<...> b;
        ...
    };
};

using an anonymous union, I should be able to use it the same exact way as before (example.value should be the value inside of union).

Then in the implementation I do the following:

template<class Derived_T>
struct Base{
    using value_type = decltype(Derived_T::value); 
    explicit Base(value_type v){
        static_cast<Derived_T*>(this)->value = v; 
    }
}

This however doesn't work:

error: Incomplete type 'Example' used in nested name specifier
>    using value_type = decltype(Derived_T::value); 

Apparently we aren't allowed to refer to a member before it has been declared. Okay... but there must be some way to extract the type data out, after all I don't care about any memory alignment or anything.

The only other thing I can think of, is include the type in the CRTP template parameter (ie Base<Derived_T, value_type>) but I want to avoid doing that. I imagine there is some method for writing a function or specifying an internal type on each derived class, I don't want to do that either (and sort of defeats the purpose of what I'm doing anyway).

Is there a way to avoid writing the constructor per class, and with out sacrificing the other code duplication minimization goals I have?

Krupip
  • 4,404
  • 2
  • 32
  • 54
  • I think you could use a template function instead of inheritance. – Gary Strivin' Apr 07 '21 at 16:18
  • Factory function – Woodford Apr 07 '21 at 16:19
  • @GaryNLOL could you elaborate? I think I still have the "Need to find type of member variable from template" problem. – Krupip Apr 07 '21 at 16:25
  • FWIW, type punning thorough a union is illegal in C++. I haven't seen a compiler not do what most people expect, but you are in UB land. depending on what you actually are doing/want, a [`std::variant`](https://en.cppreference.com/w/cpp/utility/variant) is a type safe union type that you can use. – NathanOliver Apr 07 '21 at 16:34
  • @NathanOliver I want type pruning because I'm implementing safe bitfields through them, I'm not trying to store mutually exclusive types – Krupip Apr 07 '21 at 16:40
  • @Eljay I'm confused by this question, As I said in the OP, there's more than one "Example". In my case, over 20. I don't want to have to write the same exact thing over and over again, and I *really* don't want to have to do that in two files. – Krupip Apr 07 '21 at 17:12

1 Answers1

2

Not exactly what you asked... but you can use the fact that you can use the type of D::value inside a member function... so using SFINAE over a template contructor...

I mean, you can write something as

template <typename D>
struct Base
 {
   template <typename T>
   static constexpr bool is_value_type ()
    { return std::is_same_v<decltype(D::value), T>; }

   template <typename T, bool B = is_value_type<T>(),
             std::enable_if_t<B, int> = 0>
   explicit Base (T v)
    { static_cast<D*>(this)->value = v; }
 };

where the template constructor is enabled only if the deduced type of the argument is of the same type of B::value.

Remember also to add the using

using Base<Example>::Base;

inside Example.

The following is a full compiling example

#include <type_traits>

template <typename D>
struct Base
 {
   template <typename T>
   static constexpr bool is_value_type ()
    { return std::is_same_v<decltype(D::value), T>; }

   template <typename T, bool B = is_value_type<T>(),
             std::enable_if_t<B, int> = 0>
   explicit Base (T v)
    { static_cast<D*>(this)->value = v; }
 };

struct Example : Base<Example>
 {
   using Base<Example>::Base;

   union
    {
      long value;
      long a;
      long b;
    };
 };

int main ()
 {
   //Example  e0{0};   // compilation error
   Example  e1{1l};    // compile
   //Example  e2{2ll}; // compilation error
 }
max66
  • 65,235
  • 10
  • 71
  • 111
  • I think this is exactly what I'm looking for, at least for my use case, also note, using `Base::Base;` works instead of `Base::Base;` as well. What I'm confused about is *why* this works. Why does this succeed, where also using decltype of the child's value, where straight up trying to get that type fails? Is it just because the definition of the class itself doesn't depend on the specific type anymore or something? – Krupip Apr 07 '21 at 18:16
  • 1
    @hythis - There are point of the class where you don't know completely ("incomplete type") how a template parameter is made (for example: if there is a `value` member or the type of that member). So `decltype(D::value)` works inside the **body** of the method but you can't use it to construct the signature of the method itself. – max66 Apr 07 '21 at 18:39
  • I believe there's a bit of **undefined behavior** since objects are constructed from their base-most to their most-derived order, so accessing the `value` in the `Base` constructor is accessing a member variable of a not-yet-constructed object. Maybe won't have any problems in practice, but something to be aware of. – Eljay Apr 07 '21 at 19:18