Reputation: 18228
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?
Upvotes: 1
Views: 1562
Reputation: 25367
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));
}
};
Upvotes: 1
Reputation: 93364
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);
}
Upvotes: 2