I'm trying to implement std::variant
wrapper that would behave similarly to std::variant
when it comes to assignment and creation but also allow implicit cast to all alternatives held by the variant.
This implementation gives me errors and is trying to use operator ()
to make Variant out of Variant.
class Settings
{
public:
class Variant
{
public:
using variant_t = std::variant<std::monostate, int, float, bool, std::string>;
Variant() = default;
template <typename T>
Variant(T&& v) : value(v) {}
template <typename T>
operator T() const
{
if (std::holds_alternative<T>(value)) {
return std::get<T>(value);
}
return T();
}
template <typename T>
constexpr T get() const
{
return static_cast<T>(this);
}
bool GetBool() const { return get<bool>(); };
float GetFloat() const { return get<float>(); };
int GetInt() const { return get<int>(); };
std::string GetString() const { return get<std::string>(); };
private:
variant_t value;
};
Variant GetValue(std::string_view key) const
{
std::shared_lock lock(mtx);
auto iter = data.find(key);
return iter == data.end() ? Variant() : iter->second;
};
void SetValue(std::string_view key, Variant value)
{
std::unique_lock lock(mtx);
data.insert_or_assign(key, value);
};
private:
using SettingsMap = std::unordered_map<std::string_view, Variant>;
Settings();
~Settings() {};
mutable std::shared_mutex mtx;
SettingsMap data;
};
What needs to be changed to allow assignment/conversion like this:
Variant floatSetting = 0.5f;
float fVar = floatSetting;
Variant boolSetting = true;
if (boolSetting) { return true; };
And to make this class work in general (map insert, returning from functions, passing as argument etc).
The greatest issue that prevents your code from working is that the forwarding constructor is unconstrained. A forwarding constructor always needs to be constrained because it competes with the copy and move constructor. In fact, in the scenario:
Variant a = /* ... */;
Variant b = a;
... the forwarding constructor wins in overload resolution against the copy and move constructor.
This scenario also happens down in GetValue
Therefore, you need to write something like:
template <typename T, std::enable_if_t<!std::is_same_v<std::decay_t<T>, Variant>, int> = 0>
Variant(T&& v) : value(v) {}
Having an unconstrained conversion operator template is also extremely fishy.
Your operator T
would work for cases such as T = variant_t
, or any arbitrary type that's not in the variant.
You should make sure that this template cannot be used for such cases, with:
template <typename T, std::enable_if_t<!std::is_same_v<std::decay_t<T>, variant_t>, int> = 0>
operator T() const
However, even if it worked, I wouldn't do any of this.
Implicit conversions are generally frowned upon.
They are especially bad here because std::get
will throw an exception, so innocent looking code like
float fVar = floatSetting;
... can actually throw std::bad_variant_access
.