1

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;
}
8
  • 4
    Isn't const variant sufficient in your case? Commented Oct 8, 2023 at 18:35
  • 4
    Not directly relevant, but you should be going the other way: the const version should be primary, and non-const version implemented in terms of it. const_cast<foobar_manager*>(this) exhibits undefined behavior when *this object is actually defined as const. Commented Oct 8, 2023 at 18:36
  • @jarod42, this is example code for SO. In my real code the variant holds reference_wrappers to types or reference_wrappers to const types, where the reference wrapper types are used all over the place as the objects are parts of graph-like structures that are owned by another object. So in this case const variant would make sense but the actual case involves a deeper nesting of const-ness Commented Oct 8, 2023 at 18:42
  • 1
    std::experimental/propagate_const might help for the const std::variant. Commented Oct 8, 2023 at 18:44
  • 1
    @jwezorek The const cast has no UB in your example but if the primary implementation is the const version you guard yourself against modifying the object. If you did modify the object then the const overload could potentially be UB. Commented Oct 8, 2023 at 19:39

1 Answer 1

1

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());
    }
};
Sign up to request clarification or add additional context in comments.

2 Comments

the deducing this version is interesting (and I can use C++23 features as long as Microsoft has implemented them) but I don't think it helps my actual use case. I had wanted one of the versions of the function returning the variant to be a virtual member function and then to implement the other one as a non-virtual member function in the base class in terms of the other one but that would mean I do need to convert the variant. Anyway, thanks for the answer, I may just refactor to not use inheritance here .
@jwezorek: as mentioned above in the comments, I would then also set up the const version as the to-be-implemented function (and also consider CRTP). If you then know that, say, always a foo is created, you can use std::get<0>(get_foobar()). Otherwise, you can and should use a visit as you showed in the OP ... what is actually your problem with this visit? Particularly for many derived classes, it saves you a lot of code. I'd say the use of visit alone is no reason for a refactoring.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.