How to make `std::conditional_t` work with recursion?

135 Views Asked by At

My use case is as follows:

template <typename T>
struct peel {
    using type = std::conditional_t<std::is_pointer_v<T>, typename peel<std::remove_pointer_t<T>>::type, T>;
};

template <typename T>
using peel_t = typename peel<T>::type;

However if you call this with peel_t<int *> you get back the error:

error: no type named 'type' in 'peel<int>'

The problem seems to be the lack of a terminating condition because peel is not specialized to handle the case where there are no more pointers left to remove. But in my head std::conditional_t should already be taking care of this.

My question is, is there a way to make std::conditional_t work in scenarios where recursion is required (such as this one)? In particular I want the following check to pass:

static_assert(std::is_same_v<peel_t<int **>, int>);
1

There are 1 best solutions below

0
On BEST ANSWER

conditional_t does not "take care of this" because it is not like a ternary ?: that only evaluates the chosen branch. It is instead like a call to a function

auto conditional(bool b, auto x, auto y) {
  if(b) return x; else return y;
}
// imagine conditional_t<B, T, F> <=> conditional(B, T, F)

Both arguments are evaluated first and you only get to choose one of the results afterwards. If one of the evaluations gives an error, the whole conditional is an error.

Now, for values, even though b ? f() : g() is not equivalent to conditional(b, f(), g()), it is equivalent to conditional(b, f, g)(). Reuse this idea of keeping the expressions you want to choose between unevaluated and only calling the right one afterwards. This entails replacing the branches of the conditional with unevaluated (i.e. no ::type) type traits.

template <typename T>
struct peel : std::conditional<
    std::is_pointer_v<T>,
    peel<std::remove_pointer_t<T>>,
    std::type_identity<T>
>::type {};

template<typename T>
using peel_t = peel<T>::type;

This transformation is general and does not require you to reimplement (or even know) the logic in the type traits you are trying to call (in this case std::remove_pointer).