c++castingtype-conversion

how to cast one type to another while preserving value category and cv-qualifiers?


As the title says. C++ already has std::forward, but it can't cast the original type to another. I wonder if there is a function to cast one type to another while preserving the value category and cv-qualifers of the original type.

Supposing there is such a function called perfect_cast to do the magic, the output of the following code

#include <iostream>
#include <type_traits>
#include <format>

template <typename To, typename From>
decltype(auto) perfect_cast(From&& s)
{
    // ???
}

template <std::size_t I>
struct A
{
    void f()&
    {
        std::cout << std::format("lvalue A{:d}", I) << std::endl;
    }
    void f() const&
    {
        std::cout << std::format("const lvalue A{:d}", I) << std::endl;
    }
    void f()&&
    {
        std::cout << std::format("rvalue A{:d}", I) << std::endl;
    }
    void f() const&&
    {
        std::cout << std::format("const rvalue A{:d}", I) << std::endl;
    }
};

template <std::size_t ...Is>
struct B : A<Is>... {};

auto g1()
{
    return B<0, 1, 2, 3>{};
}

const auto g2()
{
    return B<0, 1, 2, 3>{};
}

int main()
{
    B<0, 1, 2, 3> b;
    auto& b1 = b;
    perfect_cast<A<0>>(b1).f();
    const auto& b2 = b1;
    perfect_cast<A<1>>(b2).f();
    perfect_cast<A<2>>(g1()).f();
    perfect_cast<A<3>>(g2()).f();
}

should be

lvalue A0
const lvalue A1
rvalue A2
const rvalue A3

Solution

  • There aren't a lot of use cases for a combined conversion-forward. Arguably, most casts result in prvalues, and for derived-to-base casts, the language already permits treating the derived as base, preserving value category.

    For example, the OP main would be more clearly written as:

    B<0, 1, 2, 3> b;
    auto& b1 = b;
    b1.A<0>::f();
    const auto& b2 = b1;
    b2.A<1>::f();
    g1().A<2>::f();
    g2().A<3>::f();
    

    See https://godbolt.org/z/Kff9zKPha

    Perhaps it is useful to know there is a seemingly related problem of forwarding an object x in the manner of some other object y. This is useful (say, if x is "part" of y). That is useful enough that it was standardized as std::forward_like. (It is more complex than it should have to be, because of some past poor language design decisions.)