Is there a simpler way to convert a variant to another variant with const versions of the same types?

113 Views Asked by At

In the code below, is there a way to implement const_foobar foobar_manager::get_foobar() const in terms of the non-const version without the visit or equivalent code?

#include <variant>
#include <string>

struct foo {
    int val;
    std::string str;
};

struct bar {
    double val;
    std::string str;
};

using foobar = std::variant<foo, bar>;
using const_foobar = std::variant<const foo, const bar>;

class foobar_manager {
public:
    foobar get_foobar() {
        // the primary implementation of whatever needs to
        // happen to get the variant we want would go here.
        return foo{ 42, "mumble" };
    }

    const_foobar get_foobar() const {

        // we want to get the const variant in terms of the
        // the getting logic for the non-const case. Can
        // it be done without a visit?

        return std::visit(
            [](auto v)->const_foobar {
                return v;
            },
            const_cast<foobar_manager*>(this)->get_foobar()
        );
    }
};

int main() {
    foobar_manager fm;
    const auto& const_fm = fm;

    auto test1 = fm.get_foobar();
    auto test2 = const_fm.get_foobar();

    return 0;
}
1

There are 1 best solutions below

2
davidhigh On BEST ANSWER

As already mentioned in the comments, you can use use the great C++23 feature "deducing this":

#include <variant>
#include <string>
#include <type_traits>

struct foo
{
    int val;
    std::string str;
};

struct bar
{
    double val;
    std::string str;
};

using foobar = std::variant<foo, bar>;
using const_foobar = std::variant<const foo, const bar>;

struct foobar_manager
{
    auto get_foobar(this auto&& self)
    {
        if constexpr(std::is_const_v<decltype(self)>)
        {
            return const_foobar(foo(42, "mumble"));
        }
        else
        {
            return foobar(foo(42, "mumble"));
        }
    }
};

int main() {
    foobar_manager fm;
    const auto& const_fm = fm;

    auto test1 = fm.get_foobar();
    auto test2 = const_fm.get_foobar();

    return 0;
}

Godbolt


Moreover, even with C++17 only (needed for std::variant), you can separate the complex logic to create the initial foo in a separate (maybe static) function get_foo(), and then simply call this function from the two implementations of get_foobar

struct foobar_manager
{
    //...
    static auto get_foo() { return foo(42, "mumble"); }
    //...
    auto get_foobar()
    {
        return foobar(get_foo());
    }

    auto get_foobar() const
    {
        return const_foobar(get_foo());
    }
};