Select Git revision
.clang-format
func_pattern.cxx 12.44 KiB
#include <array>
#include <functional>
#include <iostream>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
#include <cmath>
// See the excellent Fluentcpp blog series on strong types.
// https://www.fluentcpp.com/2017/05/23/strong-types-inheriting-functionalities-from-underlying/
#include "NamedType/named_type.hpp"
using namespace fluent;
namespace func {
//@{
/** Helpers.
*/
template <typename T>
struct is_tuple_impl : std::false_type {};
template <typename... Ts>
struct is_tuple_impl<std::tuple<Ts...>> : std::true_type {};
template <typename T>
struct is_tuple : is_tuple_impl<std::decay_t<T>> {};
//@}
/** Named strong type helper.
*
*/
template <typename Tag>
using Var = NamedType<double, Tag, Addable, Multiplicable, Printable,
ImplicitlyConvertibleTo<double>::templ>;
/** Forward declaration of mixin.
*
*/
template <typename>
struct ConstructedFunctionSet;
/** FunctionSet: new variables defined by functions.
*
* \param FVars should be a FVariables template class.
* \param Fs is a tuple of lambdas that calculate values of NewVars_t
* types.
*
*/
template <typename FVars, typename Fs,
typename = typename std::enable_if<is_tuple<Fs>::value>::type>
struct FunctionSet : FVars {
using IndependentVariables_t = typename FVars::IndependentVariables_t;
using InputVars_t = typename FVars::InputVars_t;
using NewVars_t = typename FVars::NewVars_t;
using Vars_t = typename FVars::Vars_t;
using VariableFuncs_t = Fs;
static const size_t N_input_vars = FVars::N_input_vars;
static const size_t N_new_vars = FVars::N_new_vars;
static const size_t N_vars = FVars::N_vars;
VariableFuncs_t functions;
FunctionSet(const FVars& u, const Fs& f) : FVars(u), functions(f) {}
FunctionSet(const FunctionSet& fs) : FVars(fs), functions(fs.functions) {}
//void Print() const {}
constexpr Vars_t ComputeValues(const IndependentVariables_t& v) const {
if constexpr (std::is_same<IndependentVariables_t, InputVars_t>::value) {
return std::tuple_cat(v, ComputeNewVars(v));
} else {
auto vars = FVars::input_variable_set.ComputeValues(v);
return std::tuple_cat(vars, ComputeNewVars(vars));
}
}
template <std::size_t I, typename F, typename Args>
static constexpr auto eval_vars2(const F& f, const Args& a) {
using Return_t = typename std::tuple_element<I, NewVars_t>::type;
return Return_t{f(a)};
}
template <typename Funcs, typename Args, std::size_t... I>
static constexpr auto eval_vars(const Funcs& f, const Args& a, std::index_sequence<I...>) {
return std::make_tuple(eval_vars2<I>(std::get<I>(f), a)...);
}
constexpr auto ComputeNewVars(const InputVars_t& v) const {
return eval_vars(functions, v, std::make_index_sequence<N_new_vars>{});
}
constexpr auto operator()(const InputVars_t& v) const { return ComputeNewVars(v); }
};
/** Definable Function Variables.
* Used to construct a FunctionSet from lambdas and wraps the lambda
* arguments with a set of standard variables.
*
* \param FVars Function variables.
* \param Mixin Mixin which adds the ability to declare/define more variables
* to FVariables..
*
*/
template <typename FVars, template <typename> class Mixin>
struct Definable : FVars {
using InputVars_t = typename FVars::InputVars_t;
using NewVars_t = typename FVars::NewVars_t;
//using FVars::Print;
template <typename... Ts>
Definable(Ts&&... t) : FVars(std::forward<Ts>(t)...) {}
template <typename... Funcs>
constexpr auto operator()(const Funcs&... fs) const noexcept {
return t2t_impl(*this, std::make_tuple(fs...), std::make_index_sequence<sizeof...(fs)>{});
}
template <typename TUP, std::size_t... I>
static constexpr auto t2t_impl(const Definable<FVars, Mixin>& fd, const TUP& t,
std::index_sequence<I...>) noexcept {
constexpr auto l_wrap = [](const auto& f) constexpr {
return [&](const InputVars_t& args) { return f(args); };
};
using type = decltype(std::make_tuple(l_wrap(std::get<I>(t))...));
return Mixin<FunctionSet<FVars, type>>(FVars{fd}, std::make_tuple(l_wrap(std::get<I>(t))...));
}
};
/** Function Variables (to be defined with a lambda).
*
* \param VarSet is the previously defined variables
* \param NewVars... is a pack of new types computed from the InputVars.
*
* Note the functions relating the new to the old have not yet been
* specified..
*
*/
template <typename VarSet, typename... NewVars>
struct FVariables {
using VarSet_t = VarSet;
using IndependentVariables_t = typename VarSet::IndependentVariables_t;
using InputVars_t = typename VarSet::Vars_t;
using NewVars_t = std::tuple<NewVars...>;
using Vars_t = decltype(std::tuple_cat(std::declval<InputVars_t>(), std::declval<NewVars_t>()));
static const size_t N_input_vars = std::tuple_size<InputVars_t>::value;
static const size_t N_new_vars = std::tuple_size<NewVars_t>::value;
static const size_t N_vars = std::tuple_size<Vars_t>::value;
VarSet_t input_variable_set;
FVariables() noexcept {}
FVariables(const VarSet& f) noexcept : input_variable_set(f) {}
FVariables(const FVariables& t) noexcept : input_variable_set(t.input_variable_set) {}
//void Print() const {
// // std::cout << __PRETTY_FUNCTION__ << "\n";
//}
};
/** Function variable specialization where the variable is an independent variable nad thus has no
* lambda to calculate it. The pack Vs should be left empty.
*/
template <typename... InVars, typename... Vs>
struct FVariables<std::tuple<InVars...>, Vs...> {
using IndependentVariables_t = std::tuple<InVars...>;
using InputVars_t = IndependentVariables_t;
using NewVars_t = std::tuple<Vs...>;
using Vars_t = decltype(std::tuple_cat(std::declval<InputVars_t>(), std::declval<NewVars_t>()));
static const size_t N_input_vars = std::tuple_size<InputVars_t>::value;
static const size_t N_new_vars = std::tuple_size<NewVars_t>::value;
static const size_t N_vars = std::tuple_size<Vars_t>::value;
constexpr FVariables() noexcept {}
//void Print() const {
// // std::cout << __PRETTY_FUNCTION__ << "\n";
//}
};
/** Mixin class for FunctionSet that adds the ability to add more variables.
* \param M is a FunctionSet< FV<FS,NewVars>, lambdas... >
*/
template <typename M>
struct ConstructedFunctionSet : M {
ConstructedFunctionSet(const ConstructedFunctionSet& fs) : M(fs) {}
template <typename... Ts>
constexpr ConstructedFunctionSet(Ts&&... t) noexcept : M(std::forward<Ts>(t)...) {}
template <typename... Vs>
constexpr auto make_vars() const noexcept {
return Definable<FVariables<M, Vs...>, ConstructedFunctionSet>(M(*this));
}
template <typename... Vs, typename... Fs>
constexpr auto add(const Fs&... fs) const noexcept {
return make_vars<Vs...>()(fs...);
}
};
template <typename T, typename... Vs>
auto make_vars() {
return Definable<FVariables<T, Vs...>, ConstructedFunctionSet>{};
}
/** Helper making independent vars using FVariables specialization.
*/
template <typename... Vs>
auto make_independent_vars() {
return Definable<FVariables<std::tuple<Vs...>>, ConstructedFunctionSet>{}();
}
template <class...>
struct is_FunctionSet : public std::false_type {};
template <class... Vs>
struct is_FunctionSet<ConstructedFunctionSet<Vs...>> : public std::true_type {};
template <class... Vs>
struct is_FunctionSet<FunctionSet<Vs...>> : public std::true_type {};
} // namespace func
struct XYZ_vec {
double x = 0.0;
double y = 0.0;
double z = 0.0;
};
template <typename Tag>
using Vec_Var = NamedType<XYZ_vec, Tag>;
int main() {
using namespace func;
using X_coord = Var<struct X_coord_tag>;
using Y_coord = Var<struct Y_coord_tag>;
using Z_coord = Var<struct Z_coord_tag>;
using A_coord = Var<struct A_coord_tag>;
using B_coord = Var<struct B_coord_tag>;
using C_coord = Var<struct C_coord_tag>;
using D_coord = Var<struct D_coord_tag>;
using E_coord = Var<struct E_coord_tag>;
using F_coord = Var<struct F_coord_tag>;
using XYZ_coord = Vec_Var<struct test_vector_variable_tag>;
using XBD_coord = Vec_Var<struct xbd_vector_variable_tag>;
// X_coord and Y_coord are the independent variables that must be provided
// to compute all the subsquently defined variables.
auto vars0 = make_independent_vars<X_coord, Y_coord>();
// Add Z_coord = x+y
auto vars1 = vars0.add<Z_coord>([](const auto& v) constexpr {
const auto& x = std::get<X_coord>(v);
const auto& y = std::get<Y_coord>(v);
return x + y;
});
auto vars2 = vars1.add<A_coord, B_coord, C_coord>(
[](const auto& v) constexpr {
const auto& x = std::get<X_coord>(v);
const auto& y = std::get<Y_coord>(v);
const auto& z = std::get<Z_coord>(v);
return x * y * z;
},
[](const auto& v) constexpr {
const auto& x = std::get<X_coord>(v);
const auto& y = std::get<Y_coord>(v);
const auto& z = std::get<Z_coord>(v);
return x + y + z;
},
[](const auto& v) constexpr {
const auto& x = std::get<X_coord>(v);
const auto& y = std::get<Y_coord>(v);
const auto& z = std::get<Z_coord>(v);
return 1.0/(x * y * z);
});
auto vars3 = vars2.add<D_coord, E_coord, F_coord>(
[](const auto& v) constexpr {
const auto& x = std::get<X_coord>(v);
const auto& y = std::get<Y_coord>(v);
const auto& z = std::get<Z_coord>(v);
const auto& a = std::get<A_coord>(v);
const auto& b = std::get<B_coord>(v);
const auto& c = std::get<C_coord>(v);
return x * y * z;
},
[](const auto& v) constexpr {
const auto& x = std::get<X_coord>(v);
const auto& y = std::get<Y_coord>(v);
const auto& z = std::get<Z_coord>(v);
const auto& a = std::get<A_coord>(v);
const auto& b = std::get<B_coord>(v);
const auto& c = std::get<C_coord>(v);
return (x + y + z)/b;
},
[](const auto& v) constexpr {
const auto& x = std::get<X_coord>(v);
const auto& y = std::get<Y_coord>(v);
const auto& z = std::get<Z_coord>(v);
const auto& a = std::get<A_coord>(v);
const auto& b = std::get<B_coord>(v);
const auto& c = std::get<C_coord>(v);
return c/(x * y * z);
});
auto values3 = vars3.ComputeValues(std::make_tuple(X_coord{2.0}, Y_coord{77.0}));
// You could do this:
//std::cout << std::get<0>(values3) << "\n";
//std::cout << std::get<1>(values3) << "\n";
//std::cout << std::get<2>(values3) << "\n";
// but this is better:
std::cout << std::get<X_coord>(values3) << "\n";
std::cout << std::get<Y_coord>(values3) << "\n";
std::cout << std::get<Z_coord>(values3) << "\n";
std::cout << std::get<A_coord>(values3) << "\n";
std::cout << std::get<B_coord>(values3) << "\n";
std::cout << std::get<C_coord>(values3) << "\n";
std::cout << std::get<D_coord>(values3) << "\n";
std::cout << std::get<E_coord>(values3) << "\n";
std::cout << std::get<F_coord>(values3) << "\n";
// Add a variable which is not a plain data type.
auto vars4 = vars3.add<XYZ_coord,XBD_coord>(
[](const auto& v) constexpr {
const auto& x = std::get<X_coord>(v);
const auto& y = std::get<Y_coord>(v);
const auto& z = std::get<Z_coord>(v);
return XYZ_vec{x, y, z};
},
[](const auto& v) constexpr {
const auto& x = std::get<X_coord>(v);
const auto& y = std::get<B_coord>(v);
const auto& z = std::get<D_coord>(v);
return XYZ_vec{x, y, z};
});
auto values4 = vars4.ComputeValues(std::make_tuple(X_coord{2.0}, Y_coord{77.0}));
std::cout << "\nThese should be the same as the first 3 variables above.\n";
std::cout << std::get<XYZ_coord>(values4).get().x << "\n";
std::cout << std::get<XYZ_coord>(values4).get().y << "\n";
std::cout << std::get<XYZ_coord>(values4).get().z << "\n";
std::cout << "\nXBD\n";
std::cout << std::get<XBD_coord>(values4).get().x << "\n";
std::cout << std::get<XBD_coord>(values4).get().y << "\n";
std::cout << std::get<XBD_coord>(values4).get().z << "\n";
}