0

Suppose we have std::tuple<Ts*...> tuple;. One of the types is T* and a class U derives from T (polymorphically). How do we define

template <typename U> T* get(const std::tuple<Ts*...>&);

so that get<U>(tuple) returns std::get<T*>(tuple)? Of course, we want get<T>(tuple) to return std::get<T*>(tuple).

prestokeys
  • 4,817
  • 3
  • 20
  • 43
  • 6
    Tuples aren't pointers. They don't (and can't) store instances of subclasses. If you tried to put a `U` into a tuple position expecting a `T`, then it stopped being a `U` the moment you did so due to [object slicing](https://stackoverflow.com/questions/274626/what-is-object-slicing). – Silvio Mayolo Feb 04 '23 at 17:06
  • Is this a typo? I'd expect the opposite, getting a reference to base when the tuple stores a derived class. – HolyBlackCat Feb 04 '23 at 17:07
  • Sorry, the types in question are all pointers. I'll edit my question. – prestokeys Feb 04 '23 at 17:08
  • 1
    you could start from libc++'s [implementation](https://github.com/llvm/llvm-project/blob/d4a8a59441052165ccdd6ca493ce124be5e80d9e/libcxx/include/tuple#L1416) and change `std::is_same` to `std::is_base_of` – Alan Birtles Feb 04 '23 at 17:10
  • If you have a base class getting a pointer to a derived class out of it has approximately zero chances of success, irrespective of whether a tuple is involved in the process. C++ simply does not work this way. Perhaps, in addition to the initial confusion your terminology is also reversed? – Sam Varshavchik Feb 04 '23 at 17:11
  • 1
    What do you want `get(tuple)` to return if the value of type `T*` turns out not to be a valid `U`? Are we doing a `dynamic_cast` here to check the runtime type? While this is *probably* doable with sufficient amounts of template chicanery, I strongly suspect it to be an [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) brought on by awkwardly-designed architecture. – Silvio Mayolo Feb 04 '23 at 17:14
  • Then my `get` function can return type `T*` then, which would be fine for my purposes. – prestokeys Feb 04 '23 at 17:14
  • 1
    Can you elaborate on the behavior you want? Given `my_get`, for any `T *` in the tuple (`T` being any base of `U`), you want to `dynamic_cast` it to `U *`, and if it passes, return the result? Or what? Can you add usage examples? – HolyBlackCat Feb 04 '23 at 17:15
  • No, return type `T*` from `get(tuple)` will be fine. I can then run a virtual method with the returned type `T*`. – prestokeys Feb 04 '23 at 17:18
  • 1
    Then use `get`. What's the problem? – n. m. could be an AI Feb 04 '23 at 17:21
  • 1
    What if the tuple contains several base classes of `U`? – Quentin Feb 04 '23 at 17:21
  • 1
    *"return type `T*` ... will be fine"* Ok, but what about the rest of my comment? How do you want select which pointer to return? `dynamic_cast`? `is_base_of`? – HolyBlackCat Feb 04 '23 at 17:35
  • Please use `@username` when replying, otherwise we don't get notifications. – HolyBlackCat Feb 04 '23 at 17:35
  • Thanks. All the advice above allowed me to come up with a solution. This was an XY problem, but I'm sure someone out there might one day need the Y solution. – prestokeys Feb 04 '23 at 17:51

1 Answers1

1

A C++20 draft which seems to be working:

template<typename U, typename... Ts>
constexpr auto get(std::tuple<Ts*...> const& tuple) {
    std::array constexpr candidates{std::derived_from<U, Ts>...};
    static_assert(std::ranges::count(candidates, true) == 1);
    return get<std::ranges::equal_range(candidates, false).size()>(tuple);
}

For some evil reason, this question is tagged as c++11 (which I'm not good at) so I tried to backport my idea. Maybe the following version is suboptimal but I hope that it at least works and this much of constexprless horror is enough.

/*
Parameters:
    the index of the current tuple type being examined
    whether it fits
    the derived type
    the next remaining tuple type
    other remaining tuple types
*/
template<std::size_t, bool, typename...> struct Helper
{ static auto constexpr value = SIZE_MAX; };
template<std::size_t i, typename U, typename T, typename... Ts>
struct Helper<i, false, U, T, Ts...>
: Helper<i + 1, std::is_base_of<T, U>::value, U, Ts...>
{};
template<std::size_t i, typename U, typename... Ts>
struct Helper<i, true, U, Ts...>
{ static auto constexpr value = i; };
template<std::size_t i, typename U, typename T, typename... Ts>
struct Helper<i, true, U, T, Ts...> {
    static auto constexpr value = i;
    static_assert(
        Helper<i + 1, std::is_base_of<T, U>::value, U, Ts...>::value == SIZE_MAX,
        "ambiguous"
    );
};

template<typename U, typename T, typename... Ts>
auto get(std::tuple<T*, Ts*...> const& tuple)
-> typename std::remove_reference<decltype(std::get<
    Helper<0, std::is_base_of<T, U>::value, U, Ts...>::value
>(tuple))>::type {
    return std::get<
        Helper<0, std::is_base_of<T, U>::value, U, Ts...>::value
    >(tuple);
}
passing_through
  • 1,778
  • 12
  • 24