The solution exists in the version 2 C++ Extensions for Library Fundamentals. It might land in the standard at some point.

See std::experimental::propagate_const:
<https://en.cppreference.com/w/cpp/experimental/propagate_const.html>

Instead of declaring the data member:
T* data;
declare it as:
std::experimental::propagate_const<T*> data;


On August 3, 2025 8:53:43 a.m. EDT, Yongwei Wu via Std-Discussion <std-discussion@lists.isocpp.org> wrote:
There was a bug in the code. forward_pointer_like should be like:

template <typename T, typename U>
constexpr auto forward_pointer_like(U* ptr)
{
    constexpr bool is_adding_const =
        is_const_v<remove_reference_t<T>>;
    if constexpr (is_adding_const != is_const_v<U>) {
        if constexpr (is_adding_const) {
            return const_cast<const U*>(ptr);
        } else {
            return const_cast<remove_const_t<U>*>(ptr);
        }
    } else {
        return ptr;
    }
}

On Sun, 3 Aug 2025 at 14:09, Yongwei Wu <wuyongwei@gmail.com> wrote:
While deducing this can be useful in some scenarios, it is prone to misuse. A simplified case is as follows:

template <typename T>
class Container {
public:
    // …
    template <typename Self>
    auto data(this Self&& self) noexcept
    {
        return self.data_;
    }

private:
    T* data_;
};


Generally you want the data() member function to give you a const T* on a const container, but this implementation will always give you T*.
I nearly wrote such code in an article, and I actually saw similar code in a recently published C++ book.

I think a utility function (as well as education materials) is useful in such cases. Modelling forward_like, probably something like:

template <typename T, typename U>
constexpr auto forward_pointer_like(U* ptr)
{
    constexpr bool is_adding_const =
        is_const_v<remove_reference_t<T>>;
    if constexpr (is_const_v<remove_reference_t<T>> !=
                  is_adding_const) {
        if constexpr (is_adding_const) {
            return const_cast<const U*>(ptr);
        } else {
            return const_cast<remove_const_t<U>*>(ptr);
        }
    } else {
        return ptr;
    }
}


Related idea: Should we just update as_const to make it generate a const T* given a T*? This could be an easy fix that allows us to simply use forward_like, but I am not sure whether it can break things.

What do you think? Did I miss something already existing in the standard?

--
Yongwei Wu
URL: http://wyw.dcweb.cn/