0xbadf00d
0xbadf00d

Reputation: 18178

Infinite recursion due to unintended template argument type deduction

Please consider the following boost::math::pdf-like implementation of a generic probability density function evaluation:

template<typename T, class Distribution>
inline typename Distribution::result_type pdf(Distribution const& d, T const& x) {
    return pdf(d, static_cast<typename Distribution::result_type>(x));
}

template<typename RealType>
RealType pdf(std::uniform_real_distribution<RealType> const& d, RealType const& x)
{
    if (d.a() <= x && x <= d.b())
        return 1 / (d.b() - d.a());
    return 0;
}

I'd like to define another pdf function which takes a distribution and a vector and evaluates the probability density of each vector component. The function should be invokable for nested vectors as well. I've tried something like this:

template<typename T, class Distribution>
inline typename Distribution::result_type pdf(Distribution const& d, std::vector<T> const& x)
{
    return std::reduce(x.begin(), x.end(), typename Distribution::result_type{ 1 },
        [&](auto const& p, auto const& x) { return p * pdf(d, x); });
}

Example code:

std::vector<std::vector<double>> x = { {1}, {2, 3}, {4} };
std::vector<double> y = { 1, 2, 3, 4 };
std::uniform_real_distribution<> d;
std::cout << pdf(d, x) << std::endl;
std::cout << pdf(d, y) << std::endl;

This works as it should be. However, if I change std::uniform_real_distribution<> d; to std::uniform_real_distribution<float> d;, then I end up in an infinite recursion for the call of pdf(d, x) (for obvious reasons). So, how do I need to change the code? Maybe such that it works for other containers as well.

Upvotes: 1

Views: 66

Answers (1)

Barry
Barry

Reputation: 303147

Instead of having two overloads for the first case (which basically do the same thing):

template<typename T, class Distribution>
inline typename Distribution::result_type pdf(Distribution const& d, T const& x) {
    return pdf(d, static_cast<typename Distribution::result_type>(x));
}

template<typename RealType>
RealType pdf(std::uniform_real_distribution<RealType> const& d, RealType const& x)
{
    if (d.a() <= x && x <= d.b())
        return 1 / (d.b() - d.a());
    return 0;
}

collapse them into one:

template <typename R, typename T, std::enable_if_t<std::is_convertible_v<T, R>, int> = 0>
auto pdf(std::uniform_real_distribution<R> const& d, T x) -> R
{
    if (d.a() <= x && x <= d.b()) {
        return 1 / (d.b() - d.a());
    }
    return 0;
}

Or simpler to just block type deduction on the 2nd argument entirely (std::type_identity is C++20 but is trivial to implement):

template <typename R>
auto pdf(std::uniform_real_distribution<R> const& d, std::type_identity_t<R> x) -> R

Upvotes: 1

Related Questions