lock3 / meta

122 stars 11 forks source link

How to write tuple get? #277

Closed rbock closed 3 years ago

rbock commented 3 years ago

Hi,

Using a couple of less busy days, I wanted to see how we would implement std::tuple with metaprogramming available?

I am currently failing to implement get:

template <std::size_t I, class... Types>
constexpr
    const typename ::sqlpp::tuple_element<I, ::sqlpp::tuple<Types...>>::type &
    get(const ::sqlpp::tuple<Types...> &t) noexcept {
  consteval {
    ->fragment { return unqualid("t").unqualid("m", %{I}); }; // This fails because it references t
  }
}

Here is the full code: https://cppx.godbolt.org/z/q1MjaY

The other thing I tried was to write a member function that returns the Ith member:

template <typename... Ts> class tuple {
//...

public:
  template <std::size_t I> constexpr auto &get() const {
    consteval {
      ->fragment { return unqualid("m", %{I}); }; // This fails because it injects code into a member function
    }
  }
};

Here is the full code: https://cppx.godbolt.org/z/PchqEW

So then I thought of pointer to data member like

  using T = ::sqlpp::tuple<Types...>;
  auto member = &T::m2;
  return t.*member; // This works just fine

But again, I cannot get it to compile with injected code:

template <std::size_t I, class... Types>
constexpr
    const typename ::sqlpp::tuple_element<I, ::sqlpp::tuple<Types...>>::type &
    get(const ::sqlpp::tuple<Types...> &t) noexcept {
  consteval {
    const auto T = reflexpr(::sqlpp::tuple<Types...>);
    ->fragment { auto member = &typename(%{T})::unqualid("m", %{I}); };
  }
  return t.*member; // Fails because of use of undeclared identifier 'member'
}

Here is the full code: https://cppx.godbolt.org/z/xnz416

Now that gives an idea how to solve the problem: Write a helper to return the pointer to member...

namespace detail
{
    template<std::size_t I, typename Type>
    constexpr auto get_ith_member_ptr()
    {
      consteval {
        const auto T = reflexpr(Type);
        ->fragment { return &typename(%{T})::unqualid("m", %{I}); };
      }
    }
}

template <std::size_t I, class... Types>
constexpr
    const typename ::sqlpp::tuple_element<I, ::sqlpp::tuple<Types...>>::type &
    get(const ::sqlpp::tuple<Types...> &t) noexcept {
  using T = ::sqlpp::tuple<Types...>;
  auto member = detail::get_ith_member_ptr<I, T>();
  return t.*member;
}

This works, see https://cppx.godbolt.org/z/WcTYYb However, I wonder if all the failures to compile mentioned above are intentional?

Thanks,

Roland

TimPhoeniX commented 3 years ago

Seems to be the same problem as #235.

Use idexpr with captured reflection of t. See https://cppx.godbolt.org/z/11a69a.

rbock commented 3 years ago

Awesome! Thanks!

DarkArc commented 3 years ago

However, I wonder if all the failures to compile mentioned above are intentional?

To follow up on this...

Here is the full code: https://cppx.godbolt.org/z/q1MjaY

This is intentional, unqualid("f") is equivalent to writing f, which would result in the same error.

Here is the full code: https://cppx.godbolt.org/z/PchqEW

This is intentional as well, fragment this { should be used in place of fragment { when writing code for a member function.

Here is the full code: https://cppx.godbolt.org/z/xnz416

This too is intentional. The context is dependent (a template) so the variable member will not be injected until template instantiation. Thus when this first parses return t.*member; is unsolvable leading to the error.

A final note, prefer idexpr to unqualid if at all possible. unqualid has much worse performance characteristics as lookup is required. Additionally, it requires a fair bit of expensive setup to function in a dependent context. This latter part should be fixable, but is no small amount of work.


Thanks for helping out @TimPhoeniX :star2:.

rbock commented 3 years ago

Thanks for following up on this!

fragment this { should be used in place of fragment { when writing code for a member function.

This seems to be missing from the wiki?

DarkArc commented 3 years ago

So it is :thinking:. I thought that was already fixed... I've added a note and will get to this soon.

rbock commented 3 years ago

Cool, thanks!

rbock commented 3 years ago

While playing with the new syntax, I also noticed an even shorter way of writing this:

template <std::size_t I, class... Types>
constexpr
    const typename ::sqlpp::tuple_element<I, ::sqlpp::tuple<Types...>>::type &
    get(const ::sqlpp::tuple<Types...> &t) noexcept {
  return t.unqualid("m", I); // <-- This is it :-)
}