Search code examples
c++pointersconstructorvariant

Is there a way to initialize members of a function to a none value to class member without default constructors?


I have some classes, having some members:

#include <variant>   

class A {
   public:
   A() {};

   private:
   B b;

   std::variant<B, C> var;
};

class B {
   public:
   B(C c) : c(c) {};

   private:
   C c;
};

class C {
   public:
   C(int val) val(val) {};

   private:
   int val;
};

Now, this does of course not compile because of two reasons: neither the class B nor variant has a default constructor. But I do not have any values for B or var yet, they will be initialized in the methods of A.

I have thought of the following:

  • Defining a default constructor for B. But this way I will have an unnecessary constructor and I will have to do the same for C as well. As I might have multiple subclasses, this will lead to a cascade of unnecessary constructors quickly. Also, I cannot to this for not self-defined classes such as std::variant.
  • Initializing the variables with dummy-values. While in practice this might work since I will overwrite the values quickly anyway, this is not very clean and can lead to some ugly bugs.
  • Using Pointers. This is probably the most realistic one and the one I find most plausible, but I really wanted to avoid pointers here for some reason. Also, when I tried it with pointers, for some reason the members of B changed weirdly after returning the member. In addition, when trying to do this with variant (like var = &C(0);), I get told

value of type "C *" cannot be assigned to an entity of type variant

Coming from Java, is there any way to just (without using pointers) initializing the values to something like null? I am aware that null does not exist is C++, but I am looking for something with the same effect / some workaround to the missing default constructors. Or is this a design-flaw in my classes and should be resolved different entirely?


Solution

  • You can use std::monostate in your variant until you've selected what type to store in it.

    std::monostate:

    Unit type intended for use as a well-behaved empty alternative in std::variant. In particular, a variant of non-default-constructible types may list std::monostate as its first alternative: this makes the variant itself default-constructible.

    Outline:

    class A {
    public:
        A() = default;
        
        A(const B& b) : var(b) {}
        
        A(const C& c) : var(c) {}
    
        A& operator=(const B& b) {
            var = b;
            return *this;
        }
    
        A& operator=(const C& c) {
            var = c;
            return *this;
        }
    
    private:
        std::variant<std::monostate, B, C> var;
    };
    

    A more generic version if you'd like to add more types to A without having to explicitly add constructors and assignment operators for them all:

    #include <type_traits>
    #include <utility>
    #include <variant>
    
    template<class... Ts>
    class A_impl {
    public:
        A_impl() = default;
        
        template<class T>
        requires std::disjunction_v<std::is_same<std::remove_cvref_t<T>, Ts>...>
        A_impl(T&& val) : var(std::forward<T>(val)) {}
    
        template<class T>
        requires std::disjunction_v<std::is_same<std::remove_cvref_t<T>, Ts>...>
        A_impl& operator=(T&& val) {
            var = std::forward<T>(val);
            return *this;
        }
    
    private:
        std::variant<std::monostate, Ts...> var;
    };
    
    using A = A_impl<B, C>;