c++const-correctnessstd-variant

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


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;
}

Solution

  • 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());
        }
    };