Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Member pointers functionality #77

Draft
wants to merge 12 commits into
base: develop
Choose a base branch
from
1 change: 1 addition & 0 deletions include/boost/pfr.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <boost/pfr/config.hpp>
#include <boost/pfr/core.hpp>
#include <boost/pfr/core_name.hpp>
#include <boost/pfr/core_memptr.hpp>
#include <boost/pfr/functions_for.hpp>
#include <boost/pfr/functors.hpp>
#include <boost/pfr/io.hpp>
Expand Down
56 changes: 56 additions & 0 deletions include/boost/pfr/core_memptr.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) 2021-2023 Denis Mikhailov
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_PFR_CORE_MEMPTR_HPP
#define BOOST_PFR_CORE_MEMPTR_HPP
#pragma once

#include <boost/pfr/detail/config.hpp>

#include <boost/pfr/detail/core_memptr.hpp>

#include <boost/pfr/detail/sequence_tuple.hpp>
#include <boost/pfr/detail/stdtuple.hpp>

#include <type_traits>
#include <utility> // metaprogramming stuff

#include <boost/pfr/core.hpp>
#include <boost/pfr/type_identity.hpp>

/// \file boost/pfr/core_memptr.hpp
/// Contains all the basic interfaces for working with member pointers of some structure \forcedlink{get_memptr}, and others.
///
/// \b Synopsis:

namespace boost { namespace pfr {

/// \brief Returns member pointer to a field with index `I` in some \aggregate with type 'T'.
///
/// \b Example:
/// \code
/// struct my_struct { int i, short s; };
/// my_struct s {10, 11};
/// assert(boost::pfr::get_memptr<0>(s) == &my_struct::i);
/// auto memptr = boost::pfr::get_memptr<1>(s);
/// s.*memptr = 0;
/// \endcode
template<std::size_t I, class T>
inline auto get_memptr(boost::pfr::type_identity<T>) noexcept
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's change it to

template<std::size_t I, class T>
inline auto get_memptr() noexcept

In that case the type_identity is not required and should be removed. The

template<std::size_t I, class T>
inline auto get_memptr(const T&) noexcept

overload is not required either

{
return detail::sequence_tuple::get<I>( detail::tie_as_memptrs_tuple<T>() );
}

/// \overload get_memptr
template<std::size_t I, class T>
inline auto get_memptr(const T&) noexcept
{
// TODO: test it without default constructor
return get_memptr<I>(boost::pfr::type_identity<T>{});
}

}} // namespace boost::pfr

#endif // BOOST_PFR_CORE_MEMPTR_HPP
37 changes: 37 additions & 0 deletions include/boost/pfr/detail/align.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) 2021-2023 Glen Joseph Fernandes, Denis Mikhailov
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)


// Initial implementation was taken from Boost.Align library by Glen Joseph Fernandes, https://github.com/glenfe
// See https://github.com/boostorg/align/blob/boost-1.83.0/include/boost/align/detail/align.hpp for more details
//

#ifndef BOOST_PFR_DETAIL_ALIGN_HPP
#define BOOST_PFR_DETAIL_ALIGN_HPP
#pragma once

#include <boost/pfr/detail/config.hpp>
#include <cstddef> // for std::size_t

namespace boost { namespace pfr { namespace detail {

constexpr std::size_t align_offset(std::size_t alignment, std::size_t size, std::size_t& offset, std::size_t& space) noexcept
{
//BOOST_ASSERT(boost::alignment::detail::is_alignment(alignment)); TODO enable
if (size <= space) {
std::size_t p = ~(alignment - 1) & (offset + alignment - 1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

provide a more clear names for local variables instead of p and n

std::size_t n = p - offset;
if (n <= space - size) {
offset = p;
space -= n;
return p;
}
}
return (size_t)-1;
}

}}} // namespace boost::pfr::detail

#endif // BOOST_PFR_DETAIL_ALIGN_HPP
95 changes: 95 additions & 0 deletions include/boost/pfr/detail/core_memptr.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright (c) 2021-2023 Denis Mikhailov
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_PFR_DETAIL_CORE_MEMPTR_HPP
#define BOOST_PFR_DETAIL_CORE_MEMPTR_HPP
#pragma once

#include <boost/pfr/detail/config.hpp>
#include <boost/pfr/detail/core.hpp>
#include <boost/pfr/detail/fields_count.hpp>
#include <boost/pfr/detail/sequence_tuple.hpp>
#include <boost/pfr/detail/make_integer_sequence.hpp>
#include <boost/pfr/detail/align.hpp>
#include <boost/pfr/detail/memptr_cast.hpp>

// Each core_memptr provides `boost::pfr::detail::tie_as_memptrs_tuple` and
// `boost::pfr::detail::for_each_memptr_dispatcher` functions.
//
// The whole memptr's functionality in PFR library is build on top of those two functions.

namespace boost { namespace pfr { namespace detail {

template <class... Args>
constexpr auto make_sequence_tuple(Args... args) noexcept {
return sequence_tuple::tuple<Args...>{ args... };
}

template <std::size_t I, class T>
using tuple_element_ = sequence_tuple::tuple_element<I, decltype( detail::tie_as_tuple(std::declval<T&>()) ) >;

template <std::size_t I, class T>
using tuple_memptr_t = typename detail::tuple_element_<I, T>::type T::*;


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

template<class T, class I>
constexpr std::size_t tie_as_offsets_tuple_impl_apply(I i, std::size_t& offset, std::size_t& space) noexcept
{
using element_type = typename detail::tuple_element_<decltype(i)::value, T>::type;
std::size_t aligned = detail::align_offset(alignof(element_type), sizeof(element_type), offset, space);
offset += sizeof(element_type);
space -= sizeof(element_type);
return aligned;
}

template<class T, std::size_t... I>
constexpr auto tie_as_offsets_tuple_impl(std::index_sequence<I...>, std::size_t& offset, std::size_t& space) noexcept
{
return detail::make_sequence_tuple( tie_as_offsets_tuple_impl_apply<T>(size_t_<I>{}, offset, space)... );
}

template<class T>
constexpr auto tie_as_offsets_tuple_impl(std::index_sequence<>, std::size_t& offset, std::size_t& space) noexcept
{
// TODO: test for empty structure
(void)offset;
(void)space;
return detail::make_sequence_tuple();
}

template<class T>
constexpr auto tie_as_offsets_tuple() noexcept
{
// TODO: discard structures with non-standard alignment and bit fields
std::size_t offset = 0;
std::size_t space = sizeof(T);

return tie_as_offsets_tuple_impl<T>(detail::make_index_sequence<detail::fields_count<T>()>{}, offset, space);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a namespace to tie_as_offsets_tuple_impl to avoid ADL

}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

template<class T, std::size_t... I>
inline auto tie_as_memptrs_tuple_impl(std::index_sequence<I...>) noexcept
{
constexpr auto offsets = tie_as_offsets_tuple<T>();
return detail::make_sequence_tuple( detail::memptr_cast<detail::tuple_memptr_t<I, T>>(size_t_<sequence_tuple::get<I>(offsets)>{})... );
}

template <class T>
inline auto tie_as_memptrs_tuple() noexcept {
static_assert(
!std::is_union<T>::value,
"====================> Boost.PFR: For safety reasons it is forbidden to reflect unions. See `Reflection of unions` section in the docs for more info."
);

return tie_as_memptrs_tuple_impl<T>(detail::make_index_sequence<detail::fields_count<T>()>{});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a namespace to tie_as_memptrs_tuple_impl to avoid ADL

}

}}} // namespace boost::pfr::detail

#endif // BOOST_PFR_DETAIL_CORE_MEMPTR_HPP
45 changes: 45 additions & 0 deletions include/boost/pfr/detail/memptr_cast.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) 2021-2023 Denis Mikhailov
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_PFR_DETAIL_MEMPTR_CAST_HPP
#define BOOST_PFR_DETAIL_MEMPTR_CAST_HPP
#pragma once

#include <boost/pfr/detail/config.hpp>
#include <cstdint>
#include <limits>

namespace boost { namespace pfr { namespace detail {

constexpr std::uint8_t unsigned_by_size(size_t_<1>) noexcept { return 0; }
constexpr std::uint16_t unsigned_by_size(size_t_<2>) noexcept { return 0; }
constexpr std::uint32_t unsigned_by_size(size_t_<4>) noexcept { return 0; }
constexpr std::uint64_t unsigned_by_size(size_t_<8>) noexcept { return 0; }

template<typename T, std::size_t I>
inline T memptr_cast(size_t_<I> offset)
{
using raw_type = decltype(unsigned_by_size(size_t_<sizeof(T)>{ }));

static_assert(
sizeof(raw_type) <= sizeof(std::size_t),
"====================> Boost.PFR: Internal error while casting offset to member pointer."
);
static_assert(
decltype(offset)::value <= (std::size_t)(std::numeric_limits<raw_type>::max)(),
"====================> Boost.PFR: Internal error while casting offset to member pointer: overflow was detected"
);

union {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's an UB. Just use std::memcpy. The efficiency would remain the same - compilers know how to optimize std::memcpy

T memptr;
raw_type offset_;
};
offset_ = static_cast<raw_type>(offset);
return memptr;
}

}}} // namespace boost::pfr::detail

#endif // BOOST_PFR_DETAIL_MEMPTR_CAST_HPP
22 changes: 22 additions & 0 deletions include/boost/pfr/type_identity.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) 2021-2023 Denis Mikhailov
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_PFR_TYPE_IDENTITY_HPP
#define BOOST_PFR_TYPE_IDENTITY_HPP
#pragma once

#include <boost/pfr/detail/config.hpp>

namespace boost { namespace pfr {

template< class T >
struct type_identity {
using type = T;
};

}} // namespace boost::pfr


#endif // BOOST_PFR_TYPE_IDENTITY_HPP
20 changes: 20 additions & 0 deletions test/core/compile-fail/packed.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) 2023 Denis Mikhailov
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#include <boost/pfr/core_memptr.hpp>

#pragma pack(1)
struct Foo
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Provide the same test but with a structure like this:

#pragma pack(1)
struct sample
{
    int ch;
    int id;
    int opt;
    int value;
};

In order to test the case when settled alignment doesn't affect the size.

BTW default align is not always sizeof(int). You should provide some logic for select appropriate type between int, long, etc and for do nothing when nothing was selected for this platform.

{
char ch;
short id;
short opt;
int value;
};

int main() {
(void)boost::pfr::get_memptr<3>(boost::pfr::type_identity<Foo>{}); // Must be a compile time error
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tabs

}

42 changes: 42 additions & 0 deletions test/core/run/memptr.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) 2021-2023 Denis Mikhailov
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#include <boost/pfr/core_memptr.hpp>
#include <boost/core/lightweight_test.hpp>

struct Foo
{
char ch;
short id;
short opt;
int value;
};

int main() {

auto ch_memptr = boost::pfr::get_memptr<0>(boost::pfr::type_identity<Foo>{});
auto id_memptr = boost::pfr::get_memptr<1>(boost::pfr::type_identity<Foo>{});
auto opt_memptr = boost::pfr::get_memptr<2>(boost::pfr::type_identity<Foo>{});
auto value_memptr = boost::pfr::get_memptr<3>(boost::pfr::type_identity<Foo>{});

BOOST_TEST_EQ(ch_memptr, &Foo::ch);
BOOST_TEST_EQ(id_memptr, &Foo::id);
BOOST_TEST_EQ(opt_memptr, &Foo::opt);
BOOST_TEST_EQ(value_memptr, &Foo::value);

auto obj = Foo{};

obj.*ch_memptr = 'c';
obj.*id_memptr = 100;
obj.*opt_memptr = 200;
obj.*value_memptr = 3000;

BOOST_TEST_EQ(obj.ch, 'c');
BOOST_TEST_EQ(obj.id, 100);
BOOST_TEST_EQ(obj.opt, 200);
BOOST_TEST_EQ(obj.value, 3000);

return boost::report_errors();
}