Info
-
Did you know about python's named tuples?
Example
from collections import namedtuple
nt = namedtuple("price", "size")
nt.price = 42;
nt.size = 100;
assert 42 == nt.price and 100 == nt.size
Puzzle
- Can you implement a C++ version of named tuples?
template<class... Ts>
struct namedtuple; // TODO
int main() {
using namespace boost::ut;
"named tuple"_test = [] {
should("allow empty") = [] {
const auto nt = namedtuple{};
expect(not [](auto t) { return requires { t[""_t]; }; }(nt));
};
should("support direct initialization") = [] {
const auto nt = namedtuple{"price"_t = 42, "size"_t = 100};
expect([](auto t) { return requires { t["price"_t]; }; }(nt));
expect([](auto t) { return requires { t["size"_t]; }; }(nt));
expect(not [](auto t) { return requires { t["quantity"_t]; }; }(nt));
expect(42_i == nt["price"_t] and 100_i == nt["size"_t]);
};
should("support modification") = [] {
auto nt = namedtuple{"price"_t = int{}, "size"_t = std::size_t{}};
nt["price"_t] = 12;
nt["size"_t] = 34u;
expect(12_i == nt["price"_t] and 34_u == nt["size"_t]);
};
};
}
Solutions
template <char... Cs>
struct named_field {
private:
static constexpr char name_storage[] = {Cs...};
template <class T>
struct value_wrapper {
static constexpr auto name() { return named_field::name; }
T value{};
};
public:
static constexpr auto name = std::string_view{std::data(name_storage), std::size(name_storage)};
template <class T>
constexpr auto operator=(const T& t) { return value_wrapper<T>{.value = t}; }
};
template <class TChar, TChar... Cs> constexpr auto operator""_t () {
return named_field<Cs...>{};
}
template <class... Ts>
struct namedtuple : Ts... {
private:
struct no_value {};
friend constexpr auto operator,(no_value v, no_value) { return v; }
template <class T> friend constexpr auto operator,(no_value, T&& t) -> decltype(auto) { return std::forward<T>(t); }
template <class T> friend constexpr auto operator,(T&&t, no_value) -> decltype(auto) { return std::forward<T>(t); }
// need P0847 "deducing this"
template <class... Us, char... Cs>
constexpr auto lookup(const named_field<Cs...>&) const -> decltype(auto) {
return ([&] <class U> () -> decltype(auto) {
if constexpr (U::name() == named_field<Cs...>::name) {
return (static_cast<U&>(const_cast<namedtuple&>(*this)).value);
} else {
return no_value{};
}
}.template operator()<Us>(), ... , no_value{});
}
public:
constexpr auto operator[](const auto& f) const -> decltype(auto)
requires (not std::is_same_v<decltype(lookup<std::add_const_t<Ts>...>(f)), no_value>)
{
return lookup<std::add_const_t<Ts>...>(f);
}
constexpr auto operator[](const auto& f) -> decltype(auto)
requires (not std::is_same_v<decltype(lookup<Ts...>(f)), no_value>)
{
return lookup<Ts...>(f);
}
};
template<auto Size>
struct fixed_string {
char data[Size + 1]{};
static constexpr auto size = Size;
constexpr explicit(false) fixed_string(char const* str) { std::copy_n(str, Size + 1, data); }
constexpr explicit(false) operator std::string_view() const { return {data, Size}; }
};
template<auto Size> fixed_string(char const (&)[Size]) -> fixed_string<Size - 1>;
template<auto...>
struct arg {
template<class T> constexpr auto operator=(const T& t) const { return std::pair<arg, T>{*this, t}; }
};
template<fixed_string Str> constexpr auto operator""_t() {
return []<auto... Ns>(std::index_sequence<Ns...>) {
return arg<Str.data[Ns]...>{};
}(std::make_index_sequence<Str.size>{});
}
template<class... Ts>
struct namedtuple : std::tuple<Ts...> {
using std::tuple<Ts...>::tuple;
template<class T> constexpr decltype(auto) operator[](const T) const
requires(not std::is_void_v<boost::mp11::mp_map_find<boost::mp11::mp_list<Ts...>, T>>)
{
using type = boost::mp11::mp_map_find<boost::mp11::mp_list<Ts...>, T>;
return std::get<type>(*this).second;
}
template<class T> constexpr auto& operator[](const T)
requires(not std::is_void_v<boost::mp11::mp_map_find<boost::mp11::mp_list<Ts...>, T>>)
{
using type = boost::mp11::mp_map_find<boost::mp11::mp_list<Ts...>, T>;
return std::get<type>(*this).second;
}
};
template<class... Ts> namedtuple(Ts&&...) -> namedtuple<Ts...>;
template <class CharT, CharT... s>
struct field : private boost::mp11::mp_list_c<CharT, s...>
{
template <class T>
struct mapped_type {
using key_type = field;
constexpr mapped_type(T&& value) : value{value} {}
constexpr operator auto() { return value; }
constexpr operator auto() const { return value; }
private:
T value;
};
auto operator=(auto&& rhs) { return mapped_type{std::forward<decltype(rhs)>(rhs)}; }
};
template <class CharT, CharT... s>
constexpr auto operator""_t() { return field<CharT, s...>{}; }
template <class... Ts>
concept is_set = boost::mp11::mp_is_set<boost::mp11::mp_list<Ts...>>::value;
template <class K, class M>
concept map_contains = boost::mp11::mp_map_contains<M, K>::value;
template <class M, class K>
using map_find = boost::mp11::mp_second<boost::mp11::mp_map_find<M, std::remove_cvref_t<K>>>;
template<class... Ts> requires is_set<Ts...>
class namedtuple : Ts...
{
using map_type = boost::mp11::mp_list<boost::mp11::mp_list<typename Ts::key_type, Ts>...>;
public:
constexpr namedtuple(Ts&&... pairs) : Ts{pairs}... {}
constexpr auto& operator[](const map_contains<map_type> auto& k) {
return *static_cast<map_find<map_type, decltype(k)>*>(this);
}
constexpr auto operator[](const map_contains<map_type> auto& k) const {
return *static_cast<const map_find<map_type, decltype(k)>*>(this);
}
};
template<class... Ts> explicit namedtuple(Ts&&...) -> namedtuple<Ts...>;
template<char ... Cs>
struct KeyType
{
template< typename T>
constexpr auto operator = ( T const & v )
{
return std::make_pair( *this, v );
}
};
template<class T, T ... Cs >
constexpr auto operator"" _t()
{
return KeyType<Cs ... >{};
}
template<class... Ts>
struct namedtuple{
using M = boost::mp11::mp_list<Ts...>;
static_assert( boost::mp11::mp_is_map<M>::value );//unique keys
namedtuple( Ts ... args ):data{args...}{}
template <char ... Cs >
static constexpr bool Contain( KeyType<Cs...> const & )
{
if(sizeof ... (Cs) == 0 || sizeof...(Ts) == 0 ) return false;
using V= boost::mp11::mp_map_find<M,KeyType<Cs...>>;
return !std::is_void_v<V>;
}
template<char ... Cs > requires ( Contain( KeyType<Cs...>{} ) )
constexpr auto const & operator[]( KeyType<Cs...> const & ) const
{
using V= boost::mp11::mp_map_find<M,KeyType<Cs...> >;
return std::get<V>(data).second;
}
template<char ... Cs > requires ( Contain( KeyType<Cs...>{} ) )
constexpr auto & operator[]( KeyType<Cs...> const & )
{
using V= boost::mp11::mp_map_find<M,KeyType<Cs...> >;
return std::get<V>(data).second;
}
std::tuple< Ts ... > data;
};