Question Details

No question body available.

Tags

c++ variant c++23 perfect-forwarding explicit-object-parameter

Answers (3)

Accepted Answer Available
Accepted Answer
April 13, 2026 Score: 4 Rep: 4,811 Quality: High Completeness: 80%

I'm not sure if I understand you right, but I will propose this:

#include 
#include 

struct A { virtual ~A() = default; std::string str{"A"}; };

struct B { virtual ~B() = default; std::string str{"B"}; };

// const-correct string, adjusting return type to constness template using ccstring = std::conditionalt;

struct V : public std::variant { // returning a const-correct reference to the common type template auto&& getstring(this Self&& self) { return std::visit([](auto&& s) -> ccstring& { return s.str; }, std::forward(self)); } };

First, note that I remove the std::string inheritance, as it is not designed to be inherited.

Then, the return type of the lambda must be the same for all possibilities of the variant, in this case it's related to std::string.

My guess, then, is that you want to get a writable reference to the input object string if it's not const, and a read-only one otherwise.

Instead of letting the lambda deduce it, I propose to tell it explicitly, using a trailing return type and a helper template type ccstring that will get the correct constness from the explicit object parameter type (here Self).

This gives:

int main() {
    A a;
    B b;
    V v{a};
    std::puts(v.getstring().cstr());
    v.getstring() = "changed";
    std::puts(v.getstring().cstr());
    V const w(b);
    // w.getstring() = "changed";
    std::puts(w.getstring().cstr());
    std::puts(V{b}.getstring().cstr());
}

LIVE

As you see, the const object string is read-only.

Unfortunately, it's not as readable as you might have expected...


If you absolutely want to inherit from std::string, and thus need to upcast then, just upcast using my helper type:

struct V : public std::variant {
    // returning a const-correct reference to the common type
    template 
    auto&& getstring(this Self&& self) {
        return std::visit(
            [](auto&& s) -> ccstring& {
                return staticcast(s);
            },
            std::forward(self));
    }
};

LIVE


In answer to last OP comment, we can go further:

#include 
template 
auto&& forwardupwardlike(Derived&& x)
    requires std::derivedfrom
{
    return staticcast(
        x);
}

struct V : public std::variant { // returning a const-correct reference to the common type template auto&& getbase(this Self&& self) { return std::visit( [](auto&& s) -> decltype(auto) { return forwardupwardlike(s); }, std::forward(self)); } };

I'm casting s to its base subobject getting constness and value category from Self with std::forwardlike. But it requires an object of the appropriate type as argument, so I create it with std::declval().
Then I give the type of the std::forwardlike as argument to the staticcast.
Finally, I'm using decltype(auto) as lambda trailing return type in order to get the expected type on output and we're done.

The running example is THERE

April 13, 2026 Score: 2 Rep: 313,953 Quality: Medium Completeness: 80%

std::forwardlike is just not a useful utility here. std::forwardlike is for cases where FWD(v).i doesn't do the right thing — but here we just have a different problem: we need to explicitly specify the target type.

What we have is some kind of A (A&, A const&, A&&, or A const&&) and we need to copy the const/ref parts of that type onto std::string. Note that if A had a member that was a std::string, that case is easy since the language rules handle it for us:

[](auto&& s) -> auto&& {
    return FWD(s).str;
}

For a base class though, we need to just spell the base class correctly:

[](auto&& s) -> auto&& {
    return staticcast(s);
}

copycvreft isn't in the standard library, but is straightforward enough to implement.

(Update: I suppose copycvreft can be implemented by way of decltype(forwardlike(declval())), I think, but I would find that approach surprising)

Demo.


Incidentally, with reflection, because we can splice base classes, we'll be able to regain the language rules here:

[](auto &&s) -> auto&& {
    constexpr auto B = baseoftype(typeof(^^s), ^^std::string);
    return FWD(s).[:B:];
}

where

consteval auto baseoftype(std::meta::info derived, std::meta::info ty) -> std::meta::info {
    derived = removecvref(derived);
    ty = dealias(ty);
    for (auto base : basesof(derived, std::meta::accesscontext::unchecked())) {
        if (type_of(base) == ty) {
            return base;
        }
    }
}

Demo.

April 13, 2026 Score: 1 Rep: 69 Quality: Low Completeness: 50%

You should not make the cast depend on self. The cast target is simply the common base type (std::string). Forwarding of cv/ref qualifiers should be handled separately via std::forwardlike.

The correct and idiomatic solution is:

auto&& getstring(this auto&& self) {
  return std::visit(
      [&](auto&& s) -> decltype(auto) {
        return std::forwardlike(
            *staticcast(&s)
        );
      },
      std::forward(self));
}

Do not try to encode forwarding semantics into the cast type. Keep the cast simple and let std::forward_like handle value category and qualifiers.