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?
CodePudding user response:
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 liststd::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>;