1

I want to write a function which takes an arbitrary number of functions of the form float(float, float) and yields a callable object (e.g. a lambda expression) which represents the product (in the mathematical sense) of these functions.

We can do this, for example, in the following way:

template<typename... BinaryFunctions>
auto product_of(BinaryFunctions... fs) {
    return [=](float x, float y) { return (... * fs(x, y)); };
}

However, until C++17, the returned lambda is not a constexpr even when the fs are. So, the question is: I want to keep the fold expression, but how can I modify product_of such that the returned callable object is a constexpr in the sense of C++14?

0xbadf00d
  • 17,405
  • 15
  • 67
  • 107

2 Answers2

2

If you can use fold expressions, it means that your compiler supports C++17. If your compiler supports C++17, lambda expressions are implicitly constexpr where possible. I'm assuming that you need a C++14 solution.


Just remember that lambda expressions are just syntactic sugar for function objects with overloaded operator().

Here's a C++14 solution that uses initializer_list for the variadic expansion.

template <typename... TFs>
struct product_of_fn : TFs...
{
    template <typename... TFFwds>
    constexpr product_of_fn(TFFwds&&... fs) : TFs(std::forward<TFFwds>(fs))... { }

    constexpr auto operator()(float x, float y)
    {
        std::initializer_list<float> results{static_cast<TFs&>(*this)(x, y)...};
        float acc = 1;
        for(auto x : results) acc *= x;
        return acc;
    }
};

Usage:

template<int>
struct adder
{
    constexpr auto operator()(float x, float y){ return x + y; }
};

template<typename... TFs>
constexpr auto product_of(TFs&&... fs) {
    return product_of_fn<std::decay_t<TFs>...>(std::forward<TFs>(fs)...);
}

int main()
{
    auto f = product_of(adder<0>{}, adder<1>{});

    static_assert(f(1, 2) == 3 * 3);
}

live example on wandbox

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • My compiler is the one shipped with Visual Studio 2017. As many other compilers, MSVC only partially supports C++17 until now. So, as I've written in the question, I want to keep the fold expression, but achieve `constexpr`ness in the sense of C++14 (since MSVC doesn't support `constexpr` lambdas). – 0xbadf00d Apr 05 '17 at 23:11
  • 2
    @0xbadf00d I don't understand the obsession with fold expressions that you want to bend over backwards to do it. – Justin Apr 05 '17 at 23:16
  • @Vittorio Why do you derive from the functions? Why not use a `std::tuple`? It just seems like a defect to me - you can't have final function objects and you can't use the same function class twice. – Justin Apr 05 '17 at 23:32
  • 1
    @Justin empty base optimization I'd guess. – Yakk - Adam Nevraumont Apr 06 '17 at 22:56
1

If you really want to bend over backwards to use a fold expression, you can still do it. You will need an auxiliary function class:

#include <tuple>
#include <utility>

template<typename... Fs>
class product_of_fn
{
    std::tuple<Fs...> fs;
public:
    template<typename... FsFwd>
    constexpr explicit product_of_fn(FsFwd &&... fs)
        : fs{ std::forward<FsFwd>(fs)... }
    {}

    constexpr auto operator()(float x, float y) {
        return impl(x, y, std::make_index_sequence<sizeof...(Fs)>{});
    }
private:
    template<std::size_t... Is>
    constexpr auto impl(float x, float y, std::index_sequence<Is...>) {
        return (... * std::get<Is>(fs)(x, y));
    }
};

Usage (using @VittorioRomeo's example)

template<typename... Fs>
constexpr auto product_of(Fs... fs) {
    return product_of_fn<std::decay_t<Fs>...>{ std::forward<Fs>(fs)... };
}

template<int>
struct adder
{
    constexpr auto operator()(float x, float y) { return x + y; }
};

int main()
{
    auto f = product_of(adder<0>{}, adder<1>{});

    static_assert(f(1, 2) == 3 * 3);
}

The idea is pretty much exactly the same as Vittorio's, except we use a std::tuple so that we can use the product_of function with function objects whose types are final, as well as multiple functions of the same type.

A std::index_sequence is used to re-obtain a parameter pack just so that we can do a fold expression.


Simply converting Vittorio's solution works as well, but with the caveats I mentioned (none of Fs can be final or the same):

#include <utility>

template<typename... Fs>
class product_of_fn : Fs...
{
public:
    template<typename... FsFwd>
    constexpr explicit product_of_fn(FsFwd &&... fs)
        : Fs{ std::forward<FsFwd>(fs) }...
    {}

    constexpr auto operator()(float x, float y) {
        return (... * static_cast<Fs &>(*this)(x, y));
    }
};
Community
  • 1
  • 1
Justin
  • 24,288
  • 12
  • 92
  • 142