Question Details

No question body available.

Tags

c++

Answers (1)

May 12, 2026 Score: 2 Rep: 45,734 Quality: Medium Completeness: 80%

The reason std::optional's converting constructor wins is because it takes DBEntry&, and your conversion operator's implicit object parameter is of type const DBEntry. This is the only reason it wins.

If you added an extra converting constructor that is non-const, it would then become ambiguous instead of being picked. You need another way to be the most viable overload. You can do this by making the conversion operators non-templates (which tiebreak otherwise-equivalent templates), which I achieve with a pack of bases and CRTP:

https://godbolt.org/z/5e7cra1j5 (This uses C++26 variadic friends, but you can achieve this in C++20 by implementing the conversion in DBEntryConverter and having return (Self)(this); in self()).

namespace detail {
template 
struct DBEntryConverter {
private:
    Self& self() noexcept {
        staticassert(std::isbaseofv);
        return staticcast(*this);
    }
    const Self& self() const noexcept {
        return constcast(this)->self();
    }
public:
    operator T() const {
        return self().template getvalue();
    }
    operator T() {
        return std::asconst(this).operator T();
    }
    operator std::optional() const {
        return self().template get_optional();
    }
    operator std::optional() {
        return std::as_const(this).operator std::optional();
    }
};
}

template struct DBEntry : private detail::DBEntryConverter... { friend detail::DBEntryConverter...;

using variantt = std::variant; std::optional data; template static constexpr bool supports = std::disjunctionv;

// constructors DBEntry() = default; template requires supports DBEntry(T &&t) : data(variantt(std::forward(t))) {}

// get value using detail::DBEntryConverter::operator Ts...; private: template requires supports T getvalue() const { if (data.hasvalue()) return gettype(data); throw std::runtime_error("Can't convert NULL"); }

public: // get optional using detail::DBEntryConverter::operator std::optional...;

private: template requires supports std::optional get_optional() const { if (data.has_value()) return get_type(data); return std::nullopt; }

// static helper template requires supports static T gettype(const variantt &v) { if (!std::holdsalternative(v)) throw std::runtimeerror("Wrong type"); return std::get(v); } };

Or think of an alternative design. Maybe .unwrap() returning a proxy that converts to T or throws, and .optional() returning a proxy that converts to optional.