C++ Logo


Advanced search

Re: [std-proposals] Proposal: std::obj_from_dmbr

From: Lénárd Szolnoki <cpp_at_[hidden]>
Date: Thu, 25 Aug 2022 09:15:54 +0100

I like this proposal. Here are some observations:

1. It meshes weirdly with const

struct S {
    int i;
    const int j;
    mutable int k;

void foo() {
    S s1{};
    std::same_as<S*> auto ptr1_1 = obj_from_dmbr(&S::i, &s1.i);
    // does not compile due to implementation:
    //std::same_as<S*> auto ptr1_2 = obj_from_dmbr(&S::j, &s1.j);
    std::same_as<S*> auto ptr1_3 = obj_from_dmbr(&S::k, &s1.k);

    const S s2{};
    // does not compile, deduction failure:
    //auto ptr2_1 = obj_from_dmbr(&S::i, &s2.i);
    // does not compile due to implementation:
    //std::same_as<S*> auto ptr2_2 = obj_from_dmbr(&S::j, &s2.j);
    //ptr2_2->i = 42;
    // compiles, but not const-correct:
    std::same_as<S*> auto ptr2_3 = obj_from_dmbr(&S::k, &s2.k);
    ptr2_3->i = 42;

I think this being inherently an unsafe operation, it might be fine not
to be const-correct in certain situations. I think the alternative
would be to always return a pointer to const, and make the user pull
the trigger with const_cast, but I wouldn't propose that. Not to
mention volatile.

The following makes all the cases compile, but leaves the `const S s2;`
case const-incorrect:

template <class Mbr, typename Obj, typename T>
requires std::is_convertible_v<Mbr*const*, T*const*>
Obj * obj_from_dmbr(Mbr Obj::*mbr_ptr, T *mbr)
    using B = details::Byte<Mbr>;

    return const_cast<Obj *>(reinterpret_cast<Obj const volatile*>(
        const_cast<B *>(reinterpret_cast<B const volatile*>(mbr))
        - details::mbr_offset<Obj, Mbr>(mbr_ptr)

2. As it will be implemented by "standard-library magic" anyway,
propose it to be constexpr. Maybe even require it to not be a constant
expression when it leads to undefined behavior. As an analogy,
downcasting with static_cast is already usable in constexpr too.

3. I'm not sure I like to have both the reference and pointer
overloads. As an analogy static_cast does work for both, but for the
pointer one it is well defined to static_cast a null pointer. So maybe
for the pointer overload make `obj_from_dmbr(ptr_to_mbr,
(Mbr*)nullptr))` well-defined and return a null pointer value.
Alternatively drop the pointer overload. I don't have a strong opinion
on this one.


On Sun, 21 Aug 2022 05:11:15 +0000 (UTC)
Walt Karas via Std-Proposals <std-proposals_at_[hidden]> wrote:

> In C++, there are two ways to use subobjects to build up an object,
> base classes and data members. A significant distinction is that, in
> a Standard-blessed way, you can derive (using static_cast) the
> address of the object from the address of a base class. This
> proposal implicitly asserts that this distinction is undesirable and
> unnecessary.
> I propose adding these two templates to the Standard Library:
> template <class Mbr, typename Obj>
> Obj & obj_from_dmbr(Mbr Obj::*mbr_ptr, Mbr &mbr);
> template <class Mbr, typename Obj>
> Obj * obj_from_dmbr(Mbr Obj::*mbr_ptr, Mbr *mbr);
> For mbr a reference/pointer to the indicated (by Mbr_ptr) data member
> of an object (of tclass Obj), these functions return a
> reference/pointer to the object. Their behavior is undefined if mbr
> does not actually refer/point to the indicated data member of an
> instance of Obj.
> Here is a model implementation: https://godbolt.org/z/1oM5rGPK8
> The effectiveness of this proposal is a bit hamstrung by unfortunate
> limitations with member pointers. For example, if array[] is an
> array within class C, I can't write &(C::array[i42]). But it would
> be possible to implement this proposal with a macro, where an
> invocation like:
> C *cp = OBJ_FROM_DMBR(C, array[i42], pointer_to_element_42_in_array);
> would work.

Received on 2022-08-25 08:16:04