Upload
others
View
9
Download
0
Embed Size (px)
Citation preview
Antony Polukhin
Полухин Антон
Better C++14 Reflections
Without Macro, Markup nor external Tooling
[Boost.]PFRhttps://github.com/apolukhin/magic_get
Better C++14 Reflections
Some structure
struct complicated_struct {
int i;
short s;
double d;
unsigned u;
};
Better C++14 Reflections 4 / 147
Something that should not work...
#include <iostream>
#include <boost/pfr/precise.hpp>
struct complicated_struct { /*...*/ };
int main() {
using namespace boost::pfr::ops;
complicated_struct s {1, 2, 3.0, 4};
std::cout << "s == " << s << std::endl; // Compile time error?
}
Better C++14 Reflections 5 / 147
But it works!..
antoshkka@home:~$ ./test
s == {1, 2, 3.0, 4}
Better C++14 Reflections 6 / 147
What's in the header?
#include <iostream>
#include <boost/pfr/precise.hpp>
struct complicated_struct { /*...*/ };
int main() {
using namespace boost::pfr::ops;
complicated_struct s {1, 2, 3.0, 4};
std::cout << "s == " << s << std::endl; // Compile time error?
}
Better C++14 Reflections 7 / 147
We need to go deeper
template <class Char, class Traits, class T>
detail::enable_not_ostreamable_t<std::basic_ostream<Char, Traits>, T>
operator<<(std::basic_ostream<Char, Traits>& out, const T& value)
{
boost::pfr::write(out, value);
return out;
}
Better C++14 Reflections 8 / 147
We need to go deeper
template <class Char, class Traits, class T>
void write(std::basic_ostream<Char, Traits>& out, const T& val) {
out << '{';
detail::print_impl<0, boost::pfr::tuple_size_v<T> >::print(out, val);
out << '}';
}
Better C++14 Reflections 9 / 147
That's suspicious....
template <class Char, class Traits, class T>
void write(std::basic_ostream<Char, Traits>& out, const T& val) {
out << '{';
detail::print_impl<0, boost::pfr::tuple_size_v<T> >::print(out, val);
out << '}';
}
Better C++14 Reflections 10 / 147
O_O
template <std::size_t FieldIndex, std::size_t FieldsCount>
struct print_impl {
template <class Stream, class T>
static void print (Stream& out, const T& value) {
if (!!FieldIndex) out << ", ";
out << boost::pfr::get<FieldIndex>(value); // std::get<FieldIndex>(value)
print_impl<FieldIndex + 1, FieldsCount>::print(out, value);
}
};
Better C++14 Reflections 11 / 147
O_O
template <std::size_t FieldIndex, std::size_t FieldsCount>
struct print_impl {
template <class Stream, class T>
static void print (Stream& out, const T& value) {
if (!!FieldIndex) out << ", ";
out << boost::pfr::get<FieldIndex>(value); // std::get<FieldIndex>(value)
print_impl<FieldIndex + 1, FieldsCount>::print(out, value);
}
};
Better C++14 Reflections 12 / 147
O_O
template <std::size_t FieldIndex, std::size_t FieldsCount>
struct print_impl {
template <class Stream, class T>
static void print (Stream& out, const T& value) {
if (!!FieldIndex) out << ", ";
out << boost::pfr::get<FieldIndex>(value); // std::get<FieldIndex>(value)
print_impl<FieldIndex + 1, FieldsCount>::print(out, value);
}
};
Better C++14 Reflections 13 / 147
O_O
template <std::size_t FieldIndex, std::size_t FieldsCount>
struct print_impl {
template <class Stream, class T>
static void print (Stream& out, const T& value) {
if (!!FieldIndex) out << ", ";
out << boost::pfr::get<FieldIndex>(value); // std::get<FieldIndex>(value)
print_impl<FieldIndex + 1, FieldsCount>::print(out, value);
}
};
Better C++14 Reflections 14 / 147
What's going on here?
/// Returns reference or const reference to a field with index `I` in aggregate T
/// Requires: <skipped>
template <std::size_t I, class T>
decltype(auto) get(const T& val) noexcept;
/// `tuple_size_v` is a template variable that contains fields count in a T
/// Requires: C++14
template <class T>
constexpr std::size_t tuple_size_v = /*...*/;
Better C++14 Reflections 15 / 147
How to count fields(the basics of PFR)
Better C++14 Reflections
Basic idea for counting fields
static_assert(std::is_pod<T>::value, "")
Better C++14 Reflections 17 / 147
Basic idea for counting fields
static_assert(std::is_pod<T>::value, "")
T { args... }
Better C++14 Reflections 18 / 147
Basic idea for counting fields
static_assert(std::is_pod<T>::value, "")
T { args... }
sizeof...(args) <= fields count
Better C++14 Reflections 19 / 147
Basic idea for counting fields
static_assert(std::is_pod<T>::value, "")
T { args... }
sizeof...(args) <= fields count typeid(args)... == typeid(fields)...
Better C++14 Reflections 20 / 147
Basic idea for counting fields
static_assert(std::is_pod<T>::value, "")
T { args... }
sizeof...(args) <= fields count typeid(args)... == typeid(fields)...
sizeof(char) == 1
sizeof...(args) <= sizeof(T)
Better C++14 Reflections 21 / 147
Basic idea for counting fields
static_assert(std::is_pod<T>::value, "")
T { args... }
sizeof...(args) <= fields count typeid(args)... == typeid(fields)...
sizeof(char) == 1
sizeof...(args) <= sizeof(T) * CHAR_BITS
Better C++14 Reflections 22 / 147
Basic idea for counting fields
static_assert(std::is_pod<T>::value, "")
T { args... }
sizeof...(args) <= fields count typeid(args)... == typeid(fields)...
sizeof(char) == 1 ???
sizeof...(args) <= sizeof(T) * CHAR_BITS
Better C++14 Reflections 23 / 147
Basic idea for counting fields
static_assert(std::is_pod<T>::value, "")
T { args... }
sizeof...(args) <= fields count typeid(args)... == typeid(fields)...
sizeof(char) == 1 ???
sizeof...(args) <= sizeof(T) * CHAR_BITS
Better C++14 Reflections 24 / 147
Ubiq
struct ubiq {
template <class Type>
constexpr operator Type&() const;
};
Better C++14 Reflections 25 / 147
Ubiq
struct ubiq {
template <class Type>
constexpr operator Type&() const;
};
int i = ubiq{};
double d = ubiq{};
char c = ubiq{};
Better C++14 Reflections 26 / 147
Done
static_assert(std::is_pod<T>::value, "")
T { args... }
sizeof...(args) <= fields count typeid(args)... == typeid(fields)...
sizeof(char) == 1 ubiq{}
sizeof...(args) <= sizeof(T) * CHAR_BITS
Better C++14 Reflections 27 / 147
Putting all together
struct ubiq_constructor {
std::size_t ignore;
template <class Type>
constexpr operator Type&() const noexcept; // Undefined
};
Better C++14 Reflections 28 / 147
Putting all together. Simplified version ]:->
// #1
template <class T, std::size_t I0, std::size_t... I>
constexpr auto detect_fields_count(std::size_t& out, std::index_sequence<I0, I...>)
-> decltype( T{ ubiq_constructor{I0}, ubiq_constructor{I}... } )
{ out = sizeof...(I) + 1; /*...*/ }
// #2
template <class T, std::size_t... I>
constexpr void detect_fields_count(std::size_t& out, std::index_sequence<I...>) {
detect_fields_count<T>(out, std::make_index_sequence<sizeof...(I) - 1>{});
}
Better C++14 Reflections 29 / 147
Putting all together. Simplified version ]:->
// #1
template <class T, std::size_t I0, std::size_t... I>
constexpr auto detect_fields_count(std::size_t& out, std::index_sequence<I0, I...>)
-> decltype( T{ ubiq_constructor{I0}, ubiq_constructor{I}... } )
{ out = sizeof...(I) + 1; /*...*/ }
// #2
template <class T, std::size_t... I>
constexpr void detect_fields_count(std::size_t& out, std::index_sequence<I...>) {
detect_fields_count<T>(out, std::make_index_sequence<sizeof...(I) - 1>{});
}
Better C++14 Reflections 30 / 147
Putting all together. Simplified version ]:->
// #1
template <class T, std::size_t I0, std::size_t... I>
constexpr auto detect_fields_count(std::size_t& out, std::index_sequence<I0, I...>)
-> decltype( T{ ubiq_constructor{I0}, ubiq_constructor{I}... } )
{ out = sizeof...(I) + 1; /*...*/ }
// #2
template <class T, std::size_t... I>
constexpr void detect_fields_count(std::size_t& out, std::index_sequence<I...>) {
detect_fields_count<T>(out, std::make_index_sequence<sizeof...(I) - 1>{});
}
Better C++14 Reflections 31 / 147
Putting all together. Simplified version ]:->
// #1
template <class T, std::size_t I0, std::size_t... I>
constexpr auto detect_fields_count(std::size_t& out, std::index_sequence<I0, I...>)
-> decltype( T{ ubiq_constructor{I0}, ubiq_constructor{I}... } )
{ out = sizeof...(I) + 1; /*...*/ }
// #2
template <class T, std::size_t... I>
constexpr void detect_fields_count(std::size_t& out, std::index_sequence<I...>) {
detect_fields_count<T>(out, std::make_index_sequence<sizeof...(I) - 1>{});
}
Better C++14 Reflections 32 / 147
Putting all together. Simplified version ]:->
// #1
template <class T, std::size_t I0, std::size_t... I>
constexpr auto detect_fields_count(std::size_t& out, std::index_sequence<I0, I...>)
-> decltype( T{ ubiq_constructor{I0}, ubiq_constructor{I}... } )
{ out = sizeof...(I) + 1; /*...*/ }
// #2
template <class T, std::size_t... I>
constexpr void detect_fields_count(std::size_t& out, std::index_sequence<I...>) {
detect_fields_count<T>(out, std::make_index_sequence<sizeof...(I) - 1>{});
}
Better C++14 Reflections 33 / 147
Putting all together. Simplified version ]:->
// #1
template <class T, std::size_t I0, std::size_t... I>
constexpr auto detect_fields_count(std::size_t& out, std::index_sequence<I0, I...>)
-> decltype( T{ ubiq_constructor{I0}, ubiq_constructor{I}... } )
{ out = sizeof...(I) + 1; /*...*/ }
// #2
template <class T, std::size_t... I>
constexpr void detect_fields_count(std::size_t& out, std::index_sequence<I...>) {
detect_fields_count<T>(out, std::make_index_sequence<sizeof...(I) - 1>{});
}
Better C++14 Reflections 34 / 147
It works!..but...
Better C++14 Reflections
Bad news!
It compiles for eternity or reaches the compiler instantiation depth limit
Better C++14 Reflections
Let's fix it! Aggregates without deleted constructors and with default constructible fields
Better C++14 Reflections 37 / 147
Let's fix it! Aggregates without deleted constructors and with default constructible fields
T{}
Better C++14 Reflections 38 / 147
Let's fix it! Aggregates without deleted constructors and with default constructible fields
T{}
T{ubiq{}}
Better C++14 Reflections 39 / 147
Let's fix it! Aggregates without deleted constructors and with default constructible fields
T{}
T{ubiq{}}
T{ubiq{}, ubiq{}}
Better C++14 Reflections 40 / 147
Let's fix it! Aggregates without deleted constructors and with default constructible fields
T{}
T{ubiq{}}
T{ubiq{}, ubiq{}}
T{ubiq{}, ubiq{}, ubiq{}}
Better C++14 Reflections 41 / 147
Let's fix it! Aggregates without deleted constructors and with default constructible fields
T{}
T{ubiq{}}
T{ubiq{}, ubiq{}}
T{ubiq{}, ubiq{}, ubiq{}}
…
T{ubiq{}, ….., ubiq{}, …..., ubiq{}}
Better C++14 Reflections 42 / 147
Let's fix it! Aggregates without deleted constructors and with default constructible fields
T{}
T{ubiq{}}
T{ubiq{}, ubiq{}}
T{ubiq{}, ubiq{}, ubiq{}}
…
T{ubiq{}, ….., ubiq{}, …..., ubiq{}}
Better C++14 Reflections 43 / 147
+ + + + + + + + + + + + + + - - - - - -
Let's fix it! Aggregates without deleted constructors and with default constructible fields have a single point after which increasing ubiq counts stop compiling:
Better C++14 Reflections 44 / 147
+ + + + + + + + + + + + + + - - - - - -
Let's fix it! Aggregates without deleted constructors and with default constructible fields have a single point after which increasing ubiq counts stop compiling:
Oh! Binary search fits perfectly:– log(N) instantiations depth– log(N) instantiations
Better C++14 Reflections 45 / 147
+ + + + + + + + + + + + + + - - - - - -
Let's fix it! Aggregates without deleted constructors and with default constructible fields have a single point after which increasing ubiq counts stop compiling.
Oh! Binary search fits perfectly:– log(N) instantiations depth– log(N) instantiations
Better C++14 Reflections 46 / 147
Let's fix it! Aggregates without deleted constructors and with default constructible fields have a single point after which increasing ubiq counts stop compiling.
Oh! Binary search fits perfectly:– log(N) instantiations depth– log(N) instantiations
Better C++14 Reflections 47 / 147
Let's fix it! Aggregates without deleted constructors and with default constructible fields have a single point after which increasing ubiq counts stop compiling.
Oh! Binary search fits perfectly:– log(N) instantiations depth– log(N) instantiations
Better C++14 Reflections 48 / 147
?
Let's fix it! Aggregates without deleted constructors and with default constructible fields have a single point after which increasing ubiq counts stop compiling.
Oh! Binary search fits perfectly:– log(N) instantiations depth– log(N) instantiations
Better C++14 Reflections 49 / 147
+
Let's fix it! Aggregates without deleted constructors and with default constructible fields have a single point after which increasing ubiq counts stop compiling.
Oh! Binary search fits perfectly:– log(N) instantiations depth– log(N) instantiations
Better C++14 Reflections 50 / 147
+
Let's fix it! Aggregates without deleted constructors and with default constructible fields have a single point after which increasing ubiq counts stop compiling.
Oh! Binary search fits perfectly:– log(N) instantiations depth– log(N) instantiations
Better C++14 Reflections 51 / 147
+ ?
Let's fix it! Aggregates without deleted constructors and with default constructible fields have a single point after which increasing ubiq counts stop compiling.
Oh! Binary search fits perfectly:– log(N) instantiations depth– log(N) instantiations
Better C++14 Reflections 52 / 147
+ -
Let's fix it! Aggregates without deleted constructors and with default constructible fields have a single point after which increasing ubiq counts stop compiling.
Oh! Binary search fits perfectly:– log(N) instantiations depth– log(N) instantiations
Better C++14 Reflections 53 / 147
+ -
Let's fix it! Aggregates without deleted constructors and with default constructible fields have a single point after which increasing ubiq counts stop compiling.
Oh! Binary search fits perfectly:– log(N) instantiations depth– log(N) instantiations
Better C++14 Reflections 54 / 147
+ - -
Let's fix it! Aggregates without deleted constructors and with default constructible fields have a single point after which increasing ubiq counts stop compiling.
Oh! Binary search fits perfectly:– log(N) instantiations depth– log(N) instantiations
Better C++14 Reflections 55 / 147
+ - -
Let's fix it! Aggregates without deleted constructors and with default constructible fields have a single point after which increasing ubiq counts stop compiling.
Oh! Binary search fits perfectly:– log(N) instantiations depth– log(N) instantiations
Better C++14 Reflections 56 / 147
+ - - -
Let's fix it! Aggregates without deleted constructors and with default constructible fields have a single point after which increasing ubiq counts stop compiling.
Oh! Binary search fits perfectly:– log(N) instantiations depth– log(N) instantiations
Better C++14 Reflections 57 / 147
+ - - -
What about other aggregates?
Better C++14 Reflections
Let's fix it (2)! Aggregates with deleted constructor or with some non default constructible fields have gaps:
Better C++14 Reflections 59 / 147
- - - + + + + + + + + + + + - - - - - -
Let's fix it (2)! Aggregates with deleted constructor or with some non default constructible fields have gaps:
We need an eager version of search that does:– log(N) instantiations depth– N instantiations
Better C++14 Reflections 60 / 147
- - - + + + + + + + + + + + - - - - - -
Let's fix it (2)! Aggregates with deleted constructor or with some non default constructible fields have gaps:
We need a bad version Binary search:– log(N) instantiations depth– N instantiations
Better C++14 Reflections 61 / 147
- - - + + + + + + + + + + + - - - - - -
Let's fix it (2)! Aggregates with deleted constructor or with some non default constructible fields have gaps:
We need a bad version Binary search:– log(N) instantiations depth– N instantiations
Better C++14 Reflections 62 / 147
Let's fix it (2)! Aggregates with deleted constructor or with some non default constructible fields have gaps:
We need a bad version Binary search:– log(N) instantiations depth– N instantiations
Better C++14 Reflections 63 / 147
Let's fix it (2)! Aggregates with deleted constructor or with some non default constructible fields have gaps:
We need a bad version Binary search:– log(N) instantiations depth– N instantiations
Better C++14 Reflections 64 / 147
Let's fix it (2)! Aggregates with deleted constructor or with some non default constructible fields have gaps:
We need a bad version Binary search:– log(N) instantiations depth– N instantiations
Better C++14 Reflections 65 / 147
Let's fix it (2)! Aggregates with deleted constructor or with some non default constructible fields have gaps:
We need a bad version Binary search:– log(N) instantiations depth– N instantiations
Better C++14 Reflections 66 / 147
Let's fix it (2)! Aggregates with deleted constructor or with some non default constructible fields have gaps:
We need a bad version Binary search:– log(N) instantiations depth– N instantiations
Better C++14 Reflections 67 / 147
1
Let's fix it (2)! Aggregates with deleted constructor or with some non default constructible fields have gaps:
We need a bad version Binary search:– log(N) instantiations depth– N instantiations
Better C++14 Reflections 68 / 147
- - + + + + + + + - - - - - - - - - - -
Let's fix it (2)! Aggregates with deleted constructor or with some non default constructible fields have gaps:
We need a bad version Binary search:– log(N) instantiations depth– N instantiations
Better C++14 Reflections 69 / 147
- - + + + + + + + - - - - - - - - - - -
How to get the field type?
Better C++14 Reflections
Getting the field type
T{ ubiq_constructor<I>{}... }
Better C++14 Reflections 71 / 147
Getting the field type
T{ ubiq_constructor<I>{}... }
ubiq_constructor<I>{}::operator Type&() const
Better C++14 Reflections 72 / 147
Getting the field type
T{ ubiq_constructor<I>{}... }
ubiq_constructor<I>{}::operator Type&() const
Type
Better C++14 Reflections 73 / 147
Getting the field type
T{ ubiq_constructor<I>{}... }
ubiq_constructor<I>{}::operator Type&() const
Type
ubiq_constructor<I>{ TypeOut& }
Better C++14 Reflections 74 / 147
Getting the field type
T{ ubiq_constructor<I>{}... }
ubiq_constructor<I>{}::operator Type&() const
Type
ubiq_constructor<I>{ TypeOut& }
Better C++14 Reflections 75 / 147
Naïve solution
POD = { (public|private|protected) + (fundamental | POD)* };
Better C++14 Reflections 76 / 147
Naïve solution
fundamental (not a pointer) int→
int output→
output[I]... Types...→
Better C++14 Reflections 77 / 147
Naïve solution
template <std::size_t I>
struct ubiq_val {
std::size_t* ref_;
template <class Type>
constexpr operator Type() const noexcept {
ref_[I] = typeid_conversions::type_to_id(identity<Type>{});
return Type{};
}
};
Better C++14 Reflections 78 / 147
Naïve solution
#define BOOST_MAGIC_GET_REGISTER_TYPE(Type, Index) \
constexpr std::size_t type_to_id(identity<Type>) noexcept { \
return Index; \
} \
constexpr Type id_to_type( size_t_<Index > ) noexcept { \
Type res{}; \
return res; \
} \
/**/
Better C++14 Reflections 79 / 147
Naïve solution
BOOST_MAGIC_GET_REGISTER_TYPE(unsigned char , 1)
BOOST_MAGIC_GET_REGISTER_TYPE(unsigned short , 2)
BOOST_MAGIC_GET_REGISTER_TYPE(unsigned int , 3)
BOOST_MAGIC_GET_REGISTER_TYPE(unsigned long , 4)
BOOST_MAGIC_GET_REGISTER_TYPE(unsigned long long , 5)
BOOST_MAGIC_GET_REGISTER_TYPE(signed char , 6)
BOOST_MAGIC_GET_REGISTER_TYPE(short , 7)
BOOST_MAGIC_GET_REGISTER_TYPE(int , 8)
BOOST_MAGIC_GET_REGISTER_TYPE(long , 9)
BOOST_MAGIC_GET_REGISTER_TYPE(long long , 10)
Better C++14 Reflections 80 / 147
Naïve solution
template <class T, std::size_t N, std::size_t... I>
constexpr auto type_to_array_of_type_ids(std::size_t* types) noexcept
-> decltype(T{ ubiq_constructor<I>{}... })
{
T tmp{ ubiq_val< I >{types}... };
return tmp;
}
Better C++14 Reflections 81 / 147
Naïve solution
template <class T, std::size_t... I>
constexpr auto as_tuple_impl(std::index_sequence<I...>) noexcept {
constexpr auto a = array_of_type_ids<T>(); // #0
return std::tuple< // #3
decltype(typeid_conversions::id_to_type( // #2
size_t_<a[I]>{} // #1
))...
>{};
}
Better C++14 Reflections 82 / 147
T to tuple
Better C++14 Reflections
T to tuple
template <size_t I, class T>
auto get(T& v) noexcept {
using tuple = decltype(as_tuple_impl<T>(...));
return ???;
}
Better C++14 Reflections 84 / 147
T to tuple
template <size_t I, class T>
auto get(T& v) noexcept {
using tuple = decltype(as_tuple_impl<T>(...));
return ???;
}
Better C++14 Reflections 85 / 147
T to tuple
template <size_t I, class T>
auto get(T& v) noexcept {
using tuple = decltype(as_tuple_impl<T>(...));
return std::get<I>(
reinterpret_cast<tuple&>(v) // UB
);
}
Better C++14 Reflections 86 / 147
T to tuple
template <size_t I, class T>
auto get(T& v) noexcept {
using tuple = decltype(as_tuple_impl<T>(...));
return std::get<I>(
reinterpret_cast<tuple&>(v) // UB :-(
);
}
Better C++14 Reflections 87 / 147
T to tuple
template <size_t I, class T>
auto get(T& v) noexcept {
using tuple = decltype(as_tuple_impl<T>(...));
return *reinterpret_cast<tuple_element_t<I, tuple>* >(
reinterpret_cast<unsigned char*>(&v) + offset_for<tuple, I>()
);
}
Better C++14 Reflections 88 / 147
T to tuple
template <size_t I, class T>
auto get(T& v) noexcept {
using tuple = decltype(as_tuple_impl<T>(...));
return *reinterpret_cast<tuple_element_t<I, tuple>* >(
reinterpret_cast<unsigned char*>(&v) + offset_for<tuple, I>()
);
}
Better C++14 Reflections 89 / 147
T to tuple
template <size_t I, class T>
auto get(T& v) noexcept {
using tuple = decltype(as_tuple_impl<T>(...));
return *reinterpret_cast<tuple_element_t<I, tuple>* >(
reinterpret_cast<unsigned char*>(&v) + offset_for<tuple, I>()
);
}
Better C++14 Reflections 90 / 147
T to tuple
template <size_t I, class T>
auto get(T& v) noexcept {
using tuple = decltype(as_tuple_impl<T>(...));
return *reinterpret_cast<tuple_element_t<I, tuple>* >(
reinterpret_cast<unsigned char*>(&v) + offset_for<tuple, I>()
);
}
Better C++14 Reflections 91 / 147
Pitfalls of naive reflection
Better C++14 Reflections
Pitfalls ● Enums are represented as an underlying type
Better C++14 Reflections 93 / 147
Pitfalls ● Enums are represented as an underlying type● Does not work with nested structures
Better C++14 Reflections 94 / 147
Pitfalls ● Enums are represented as an underlying type● Does not work with nested structures
– Or does the reflection recursively
Better C++14 Reflections 95 / 147
Pitfalls ● Enums are represented as an underlying type● Does not work with nested structures
– Or does the reflection recursively (flat reflection)
Better C++14 Reflections 96 / 147
Pitfalls ● Enums are represented as an underlying type● Does not work with nested structures
– Or does the reflection recursively (flat reflection)
struct struct0 {
int i;
short s;
};
struct struct1 {
struct0 st;
double d;
unsigned u;
};
Better C++14 Reflections 97 / 147
Pitfalls ● Enums are represented as an underlying type● Does not work with nested structures
– Or does the reflection recursively (flat reflection)
struct struct_flat {
int i;
short s;
double d;
unsigned u;
};
Better C++14 Reflections 98 / 147
Pitfalls ● Enums are represented as an underlying type● Does not work with nested structures
– Or does the reflection recursively (flat reflection)● A lot of computations to encode cv pointers as an
integer
Better C++14 Reflections 99 / 147
Pitfalls ● Enums are represented as an underlying type● Does not work with nested structures
– Or does the reflection recursively (flat reflection)● A lot of computations to encode cv pointers as an
integer● Works only with PODs
Better C++14 Reflections 100 / 147
That’s the best we can do!
Better C++14 Reflections
That’s the best we can do!...was I thinking
Better C++14 Reflections
That’s NOT the best we can do!
Better C++14 Reflections
Better field type detectionVersion #1; creepy
Better C++14 Reflections
Getting the field type
T{ ubiq_constructor<I>{}... }
ubiq_constructor<I>{}::operator Type&() const
Type
Better C++14 Reflections 105 / 147
Getting the field type
T{ ubiq_constructor<I>{}... }
ubiq_constructor<I>{}::operator Type&() const
Type
Better C++14 Reflections 106 / 147
Getting the field type
T{ ubiq_constructor<I>{}... }
ubiq_constructor<I>{}::operator Type&() const
Type
call T{ ubiq_constructor<I>{}... } again from within the operator Type&()
Better C++14 Reflections 107 / 147
Getting the field type recursively
for_each_field_in_depth<T, I, Types...>(t, callback, ... );
Better C++14 Reflections 108 / 147
Getting the field type recursively
for_each_field_in_depth<T, I, Types...>(t, callback, ... );
T{ ubiq_constructor_next<T, I>{}... }
Better C++14 Reflections 109 / 147
Getting the field type recursively
for_each_field_in_depth<T, I, Types...>(t, callback, ... );
T{ ubiq_constructor_next<T, I>{}... }
ubiq_constructor_next<I>{}::operator Type&() const
Better C++14 Reflections 110 / 147
Getting the field type recursively
for_each_field_in_depth<T, I, Types...>(t, callback, ... );
T{ ubiq_constructor_next<T, I>{}... }
ubiq_constructor_next<I>{}::operator Type&() const
{ for_each_field_in_depth<T, I+1, Types..., Type>(t, callback, ... ); }
Better C++14 Reflections 111 / 147
Getting the field type recursively
for_each_field_in_depth<T, I, Types...>(t, callback, ... );
T{ ubiq_constructor_next<T, I>{}... }
ubiq_constructor_next<I>{}::operator Type&() const
{ for_each_field_in_depth<T, I+1, Types..., Type>(t, callback, ... ); }
Better C++14 Reflections 112 / 147
Getting the field type recursively
for_each_field_in_depth<T, I, Types...>(t, callback, ... );
T{ ubiq_constructor_next<T, I>{}... }
ubiq_constructor_next<I>{}::operator Type&() const
{ for_each_field_in_depth<T, I+1, Types..., Type>(t, callback, ... ); }
...
Better C++14 Reflections 113 / 147
Getting the field type recursively
for_each_field_in_depth<T, I, Types...>(t, callback, ... );
T{ ubiq_constructor_next<T, I>{}... }
ubiq_constructor_next<I>{}::operator Type&() const
{ for_each_field_in_depth<T, I+1, Types..., Type>(t, callback, ... ); }
...
callback<Types...>(t, make_tuple_of_references<Types...>(t))
Better C++14 Reflections 114 / 147
Pitfalls ● Big compile time overhead
Better C++14 Reflections 115 / 147
Pitfalls ● Big compile time overhead● We rely on compiler cleverness for eliminating
unnecessary copies
Better C++14 Reflections 116 / 147
Pitfalls ● Big compile time overhead● We rely on compiler cleverness for eliminating
necessary copies– But no too much, so we assert that the compiler is
clever enough:
constexpr T tmp{ ubiq{I}... };
Better C++14 Reflections 117 / 147
Pitfalls ● Big compile time overhead● We rely on compiler cleverness for eliminating
unnecessary copies– But no too much, so we assert that the compiler is
clever enough:
constexpr T tmp{ ubiq{I}... };● Not always works
Better C++14 Reflections 118 / 147
Even better field type detectionVersion #2; very very creepy
Better C++14 Reflections
The essence
Better C++14 Reflections 120 / 147
The essence ● To register id type relations we need a way to save a ↔global state in metafunctions
Better C++14 Reflections 121 / 147
The essence ● To register id type relations we need a way to save a ↔global state in metafunctions
● Statefull metaprogramming is not permitted by the standard
Better C++14 Reflections 122 / 147
The essence ● To register id type relations we need a way to save a ↔global state in metafunctions
● Statefull metaprogramming is not permitted by the standard– CWG is very serious about that
Better C++14 Reflections 123 / 147
The essence ● To register id type relations we need a way to save a ↔global state in metafunctions
● Statefull metaprogramming is not permitted by the standard– CWG is very serious about that– There's even a CWG 2118 issue to deal with some
border cases of impure metafunctions
Better C++14 Reflections 124 / 147
The essence ● To register id type relations we need a way to save a ↔global state in metafunctions
● Statefull metaprogramming is not permitted by the standard– CWG is very serious about that– There's even a CWG 2118 issue to deal with some
border cases of impure metafunctions
Huh!
Better C++14 Reflections 125 / 147
Type Loophole
template <class T, std::size_t N>
struct tag {
// forward declaration of loophole(tag<T,N>) without a result type
friend auto loophole(tag<T,N>);
};
Better C++14 Reflections 126 / 147
Type Loophole
template <class T, std::size_t N>
struct tag {
// forward declaration of loophole(tag<T,N>) without a result type
friend auto loophole(tag<T,N>);
};
template <class T, class FieldType, std::size_t N, bool B>
struct fn_def {
// definition of loophole(tag<T,N>) with a result type
friend auto loophole(tag<T,N>) { return FieldType{}; }
};
Better C++14 Reflections 127 / 147
Type Loophole
template <class T, std::size_t N>
struct loophole_ubiq {
template<class U, std::size_t M> static std::size_t ins(...);
template<class U, std::size_t M, std::size_t = sizeof(loophole(tag<T,M>{})) >
static char ins(int);
template<class U, std::size_t = sizeof(fn_def<
T, U, N, sizeof(ins<U, N>(0)) == sizeof(char)
>)>
constexpr operator U&() const noexcept;
};
Better C++14 Reflections 128 / 147
Type Loophole
template <class T, std::size_t N>
struct loophole_ubiq {
template<class U, std::size_t M> static std::size_t ins(...);
template<class U, std::size_t M, std::size_t = sizeof(loophole(tag<T,M>{})) >
static char ins(int);
template<class U, std::size_t = sizeof(fn_def<
T, U, N, sizeof(ins<U, N>(0)) == sizeof(char)
>)>
constexpr operator U&() const noexcept;
};
Better C++14 Reflections 129 / 147
Type Loophole
template <class T, std::size_t N>
struct loophole_ubiq {
template<class U, std::size_t M> static std::size_t ins(...);
template<class U, std::size_t M, std::size_t = sizeof(loophole(tag<T,M>{})) >
static char ins(int);
template<class U, std::size_t = sizeof(fn_def<
T, U, N, sizeof(ins<U, N>(0)) == sizeof(char)
>)>
constexpr operator U&() const noexcept;
};
Better C++14 Reflections 130 / 147
Type Loophole
template <class T, std::size_t N>
struct loophole_ubiq {
template<class U, std::size_t M> static std::size_t ins(...);
template<class U, std::size_t M, std::size_t = sizeof(loophole(tag<T,M>{})) >
static char ins(int);
template<class U, std::size_t = sizeof(fn_def<
T, U, N, sizeof(ins<U, N>(0)) == sizeof(char)
>)>
constexpr operator U&() const noexcept;
};
Better C++14 Reflections 131 / 147
Type Loophole
template <class T, std::size_t N>
struct loophole_ubiq {
template<class U, std::size_t M> static std::size_t ins(...);
template<class U, std::size_t M, std::size_t = sizeof(loophole(tag<T,M>{})) >
static char ins(int);
template<class U, std::size_t = sizeof(fn_def<
T, U, N, sizeof(ins<U, N>(0)) == sizeof(char)
>)>
constexpr operator U&() const noexcept;
};
Better C++14 Reflections 132 / 147
Type Loophole
int main() {
struct test { char c; int i; };
test t{loophole_ubiq<test, 0>{} };
static_assert(
std::is_same<
char,
decltype( loophole(tag<test, 0>{}) )
>::value, ""
);
}
Better C++14 Reflections 133 / 147
Type Loophole
int main() {
struct test { char c; int i; };
test t{loophole_ubiq<test, 0>{} };
static_assert(
std::is_same<
char,
decltype( loophole(tag<test, 0>{}) )
>::value, ""
);
}
Better C++14 Reflections 134 / 147
Perfect field type detectionVersion #3; C++17 required
Better C++14 Reflections
C++17
template <class T>
constexpr auto as_tuple_impl(T&& val) noexcept {
constexpr auto count = fields_count<T>();
if constexpr (count == 1) {
auto& [a] = std::forward<T>(val);
return detail::make_tuple_of_references(a);
} else if constexpr (count == 2) {
auto& [a,b] = std::forward<T>(val);
return detail::make_tuple_of_references(a,b);
} // ...
}
Better C++14 Reflections 136 / 147
C++17
template <class T>
constexpr auto as_tuple_impl(T&& val) noexcept {
constexpr auto count = fields_count<T>();
if constexpr (count == 1) {
auto& [a] = std::forward<T>(val);
return detail::make_tuple_of_references(a);
} else if constexpr (count == 2) {
auto& [a,b] = std::forward<T>(val);
return detail::make_tuple_of_references(a,b);
} // ...
}
Better C++14 Reflections 137 / 147
C++17
template <class T>
constexpr auto as_tuple_impl(T&& val) noexcept {
constexpr auto count = fields_count<T>();
if constexpr (count == 1) {
auto& [a] = std::forward<T>(val);
return detail::make_tuple_of_references(a);
} else if constexpr (count == 2) {
auto& [a,b] = std::forward<T>(val);
return detail::make_tuple_of_references(a,b);
} // ...
}
Better C++14 Reflections 138 / 147
C++17
template <class T>
constexpr auto as_tuple_impl(T&& val) noexcept {
constexpr auto count = fields_count<T>();
if constexpr (count == 1) {
auto& [a] = std::forward<T>(val);
return detail::make_tuple_of_references(a);
} else if constexpr (count == 2) {
auto& [a,b] = std::forward<T>(val);
return detail::make_tuple_of_references(a,b);
} // ...
}
Better C++14 Reflections 139 / 147
Is it useful?or what features are available in [Boost.]PFR
Better C++14 Reflections
Features ● Implemented (flat and precise):– Comparisons : <, <=, >, >=, !=, ==– Heterogeneous comparators: less<>, flat_equal<>– IO stream operators: operator <<, operator>>– Hashing: flat_hash, hash– Tie to/from structure
Better C++14 Reflections 141 / 147
Features ● Implemented (flat and precise):– Comparisons : <, <=, >, >=, !=, ==– Heterogeneous comparators: less<>, flat_equal<>– IO stream operators: operator <<, operator>>– Hashing: flat_hash, hash– Tie to/from structure
● Do it on your own:– User defined serializers– Basic reflections– New type_traits: is_continuous_layout<T>,
is_padded<T>, has_unique_object_representation<T>– New features for containers: punch_hole<T, Index>– More generic algorithms: vector_mult, parse to struct
Better C++14 Reflections 142 / 147
Acknowledgementspeople who helped and invented stuff
Better C++14 Reflections
Acks ● Alexandr Poltavsky — for the Loophole● Bruno Dutra — for the recursive reflection● Chris Beck — for removing UBs from reinterpret_casts● Abutcher-gh — for adding some tie functions
● Lisa Lippincott and Alexey Moiseytsev — for finding issues with alignments
● Nikita Kniazev, Anton Bikineev and others – for testing reporting and finding issues.
Better C++14 Reflections 144 / 147
Спасибо!
Спасибо!Thanks for listening!
Antony PolukhinDeveloper in Yandex.Taxi
https://github.com/apolukhin
https://stdcpp.ru/