return false; \
} \
} \
- template<typename A> constexpr bool T { class_trait_ns_##T::trait_value<A>() };
+ template<typename A> constexpr bool T{class_trait_ns_##T::trait_value<A>()};
// Define enum class NAME with the given enumerators, a static
// function EnumToString() that maps enumerators to std::string,
static_cast<int>(e), #__VA_ARGS__); \
}
-template<typename A> std::optional<A> GetIfNonNull(const A *p) {
- if (p) {
- return {*p};
- }
- return std::nullopt;
-}
-
-// If a variant holds a value of a particular type, return a copy in a
-// std::optional<>.
-template<typename A, typename VARIANT>
-std::optional<A> GetIf(const VARIANT &u) {
- return GetIfNonNull(std::get_if<A>(&u));
-}
-
-// Collapses a nested std::optional<std::optional<A>> to std::optional<A>
-template<typename A>
-std::optional<A> JoinOptionals(std::optional<std::optional<A>> &&x) {
- if (x.has_value()) {
- return std::move(*x);
- }
- return std::nullopt;
-}
-
-// Apply a function to optional argument(s), if are all present.
-// N.B. This function uses a "functor" in the C++ sense -- a type with
-// a member function operator() -- to implement a "functor" in the category
-// theoretical sense.
-template<typename A, typename B>
-std::optional<A> MapOptional(std::function<A(B &&)> &f, std::optional<B> &&x) {
- if (x.has_value()) {
- return {f(std::move(*x))};
- }
- return std::nullopt;
-}
-
-template<typename A, typename B, typename C>
-std::optional<A> MapOptional(std::function<A(B &&, C &&)> &f,
- std::optional<B> &&x, std::optional<C> &&y) {
- if (x.has_value() && y.has_value()) {
- return {f(std::move(*x), std::move(*y))};
- }
- return std::nullopt;
-}
-
-// Move a value from one variant type to another. The types allowed in the
-// source variant must all be allowed in the destination variant type.
-template<typename TOV, typename FROMV> TOV MoveVariant(FROMV &&u) {
- return std::visit(
- [](auto &&x) -> TOV { return {std::move(x)}; }, std::move(u));
-}
-
} // namespace Fortran::common
#endif // FORTRAN_COMMON_IDIOMS_H_
#define FORTRAN_COMMON_TEMPLATE_H_
#include <functional>
+#include <optional>
#include <tuple>
#include <type_traits>
#include <variant>
-// Template metaprogramming utilities
+// Utility templates for metaprogramming and for composing the
+// std::optional<>, std::tuple<>, and std::variant<> containers.
namespace Fortran::common {
+// const A * -> std::optional<A>
+template<typename A> std::optional<A> GetIfNonNull(const A *p) {
+ if (p) {
+ return {*p};
+ }
+ return std::nullopt;
+}
+
+// const std::variant<..., A, ...> -> std::optional<A>
+// i.e., when a variant holds a value of a particular type, return a copy
+// of that value in a std::optional<>.
+template<typename A, typename VARIANT>
+std::optional<A> GetIf(const VARIANT &u) {
+ return GetIfNonNull(std::get_if<A>(&u));
+}
+
+// std::optional<std::optional<A>> -> std::optional<A>
+template<typename A>
+std::optional<A> JoinOptional(std::optional<std::optional<A>> &&x) {
+ if (x.has_value()) {
+ return std::move(*x);
+ }
+ return std::nullopt;
+}
+
+// Move a value from one variant type to another. The types allowed in the
+// source variant must all be allowed in the destination variant type.
+template<typename TOV, typename FROMV> TOV MoveVariant(FROMV &&u) {
+ return std::visit(
+ [](auto &&x) -> TOV { return {std::move(x)}; }, std::move(u));
+}
+
// SearchTypeList<PREDICATE, TYPES...> scans a list of types. The zero-based
// index of the first type T in the list for which PREDICATE<T>::value() is
// true is returned, or -1 if the predicate is false for every type in the list.
template<typename A, typename... TYPES>
constexpr int TypeIndex{SearchTypeList<MatchType<A>::template Match, TYPES...>};
-// SearchVariantType<PREDICATE> scans the types that constitute the alternatives
-// of a std::variant instantiation. The zero-based index of the first type T
-// among the alternatives for which PREDICATE<T>::value() is true is returned,
-// or -1 if the predicate is false for every alternative of the union.
-
-// N.B. It *is* possible to extract the types of the alternatives of a
-// std::variant discriminated union instantiation and reuse them as a
-// template parameter pack in another template instantiation. The trick is
-// to match the std::variant type with a partial specialization. And it
-// works with tuples, too, of course.
+// OverMembers extracts the list of types that constitute the alternatives
+// of a std::variant or elements of a std::tuple and passes that list as
+// parameter types to a given variadic template.
template<template<typename...> class, typename> struct OverMembersHelper;
template<template<typename...> class T, typename... Ts>
struct OverMembersHelper<T, std::variant<Ts...>> {
template<template<typename...> class T, typename TorV>
using OverMembers = typename OverMembersHelper<T, TorV>::type;
+// SearchMembers<PREDICATE> scans the types that constitute the alternatives
+// of a std::variant instantiation or elements of a std::tuple.
+// The zero-based index of the first type T among the alternatives for which
+// PREDICATE<T>::value() is true is returned, or -1 when the predicate is false
+// for every type in the set.
template<template<typename> class PREDICATE> struct SearchMembersHelper {
template<typename... Ts> struct Scanner {
static constexpr int value() { return SearchTypeList<PREDICATE, Ts...>; }
};
};
-
template<template<typename> class PREDICATE, typename TorV>
constexpr int SearchMembers{
OverMembers<SearchMembersHelper<PREDICATE>::template Scanner,
template<typename VARIANT>
using VariantToTuple = typename VariantToTupleHelper<VARIANT>::type;
-template<typename A, typename B, typename... REST> struct AreTypesDistinctHelper {
+template<typename A, typename B, typename... REST>
+struct AreTypesDistinctHelper {
static constexpr bool value() {
if constexpr (std::is_same_v<A, B>) {
return false;
}
if constexpr (sizeof...(REST) > 0) {
- return AreTypesDistinctHelper<A, REST...>::value() && AreTypesDistinctHelper<B, REST...>::value();
+ return AreTypesDistinctHelper<A, REST...>::value() &&
+ AreTypesDistinctHelper<B, REST...>::value();
}
return true;
}
};
-template<typename... Ts> constexpr bool AreTypesDistinct{AreTypesDistinctHelper<Ts...>::value()};
+template<typename... Ts>
+constexpr bool AreTypesDistinct{AreTypesDistinctHelper<Ts...>::value()};
template<typename> struct TupleToVariantHelper;
template<typename... Ts> struct TupleToVariantHelper<std::tuple<Ts...>> {
- static_assert(AreTypesDistinct<Ts...> || !"TupleToVariant: types are not pairwise distinct");
+ static_assert(AreTypesDistinct<Ts...> ||
+ !"TupleToVariant: types are not pairwise distinct");
using type = std::variant<Ts...>;
};
template<typename TUPLE>
template<typename... VARIANTS>
using CombineVariants = typename CombineVariantsHelper<VARIANTS...>::type;
+// SquashVariantOfVariants: given a std::variant whose alternatives are
+// all std::variant instantiations, form a new union over their alternatives.
template<typename VARIANT>
using SquashVariantOfVariants = OverMembers<CombineVariants, VARIANT>;
-// Given a type function, apply it to each of the types in a tuple or variant,
-// and collect the results in another tuple or variant.
+// Given a type function, MapTemplate applies it to each of the types
+// in a tuple or variant, and collect the results in a given variadic
+// template (typically a std::variant).
template<template<typename> class, template<typename...> class, typename...>
struct MapTemplateHelper;
-template<template<typename> class F, template<typename...> class TorV,
+template<template<typename> class F, template<typename...> class PACKAGE,
typename... Ts>
-struct MapTemplateHelper<F, TorV, std::tuple<Ts...>> {
- using type = TorV<F<Ts>...>;
+struct MapTemplateHelper<F, PACKAGE, std::tuple<Ts...>> {
+ using type = PACKAGE<F<Ts>...>;
};
-template<template<typename> class F, template<typename...> class TorV,
+template<template<typename> class F, template<typename...> class PACKAGE,
typename... Ts>
-struct MapTemplateHelper<F, TorV, std::variant<Ts...>> {
- using type = TorV<F<Ts>...>;
+struct MapTemplateHelper<F, PACKAGE, std::variant<Ts...>> {
+ using type = PACKAGE<F<Ts>...>;
};
-template<template<typename> class F, template<typename...> class TorV,
- typename TV>
-using MapTemplate = typename MapTemplateHelper<F, TorV, TV>::type;
+template<template<typename> class F, typename TorV,
+ template<typename...> class PACKAGE = std::variant>
+using MapTemplate = typename MapTemplateHelper<F, PACKAGE, TorV>::type;
+
+// std::tuple<std::optional<>...> -> std::optional<std::tuple<...>>
+// i.e., inverts a tuple of optional values into an optional tuple that has
+// a value only if all of the original elements were present.
+template<typename... A, std::size_t... J>
+std::optional<std::tuple<A...>> AllElementsPresentHelper(
+ std::tuple<std::optional<A>...> &&t, std::index_sequence<J...>) {
+ bool present[]{std::get<J>(t).has_value()...};
+ for (std::size_t j{0}; j < sizeof...(J); ++j) {
+ if (!present[j]) {
+ return std::nullopt;
+ }
+ }
+ return {std::make_tuple(*std::get<J>(t)...)};
+}
+
+template<typename... A>
+std::optional<std::tuple<A...>> AllElementsPresent(
+ std::tuple<std::optional<A>...> &&t) {
+ return AllElementsPresentHelper(
+ std::move(t), std::index_sequence_for<A...>{});
+}
+
+// (std::optional<>...) -> std::optional<std::tuple<...>>
+// i.e., given some number of optional values, return a optional tuple of
+// those values that is present only of all of the values were so.
+template<typename... A>
+std::optional<std::tuple<A...>> AllPresent(std::optional<A> &&... x) {
+ return AllElementsPresent(std::make_tuple(std::move(x)...));
+}
+
+// (f(A...) -> R) -> std::optional<A>... -> std::optional<R>
+// Apply a function to optional arguments if all are present.
+// If the function returns std::optional, you will probably want to
+// pass it through JoinOptional to "squash" it.
+template<typename R, typename... A>
+std::optional<R> MapOptional(
+ std::function<R(A &&...)> &&f, std::optional<A> &&... x) {
+ if (auto args{AllPresent(std::move(x)...)}) {
+ return std::make_optional(std::apply(std::move(f), std::move(*args)));
+ }
+ return std::nullopt;
+}
} // namespace Fortran::common
#endif // FORTRAN_COMMON_TEMPLATE_H_
Relational(std::enable_if_t<!std::is_reference_v<A>, A> &&x)
: u{std::move(x)} {}
std::ostream &Dump(std::ostream &o) const;
- common::MapTemplate<Relational, std::variant, RelationalTypes> u;
+ common::MapTemplate<Relational, RelationalTypes> u;
};
extern template struct Relational<Type<TypeCategory::Integer, 1>>;
template<typename A>
Expr(std::enable_if_t<!std::is_reference_v<A>, A> &&x) : u{std::move(x)} {}
- common::MapTemplate<Expr, std::variant, CategoryTypes<CAT>> u;
+ common::MapTemplate<Expr, CategoryTypes<CAT>> u;
};
// BOZ literal constants need to be wide enough to hold an integer or real
}
using Others = std::variant<BOZLiteralConstant>;
- using Categories = common::MapTemplate<Expr, std::variant, SomeCategory>;
+ using Categories = common::MapTemplate<Expr, SomeCategory>;
common::CombineVariants<Others, Categories> u;
};
namespace Fortran::evaluate {
+using SameRealExprPair = SameKindExprs<TypeCategory::Real>;
+
+static SameRealExprPair ConversionHelper(
+ Expr<SomeReal> &&x, Expr<SomeReal> &&y) {
+ return std::visit(
+ [&](auto &&rx, auto &&ry) -> SameRealExprPair {
+ using XTy = ResultType<decltype(rx)>;
+ using YTy = ResultType<decltype(ry)>;
+ if constexpr (std::is_same_v<XTy, YTy>) {
+ return {SameExprs<XTy>{std::move(rx), std::move(ry)}};
+ } else if constexpr (XTy::kind < YTy::kind) {
+ return {SameExprs<YTy>{ConvertTo(ry, std::move(rx)), std::move(ry)}};
+ } else {
+ return {SameExprs<XTy>{std::move(rx), ConvertTo(rx, std::move(ry))}};
+ }
+ },
+ std::move(x.u), std::move(y.u));
+}
+
ConvertRealOperandsResult ConvertRealOperands(
parser::ContextualMessages &messages, Expr<SomeType> &&x,
Expr<SomeType> &&y) {
Expr<SomeInteger> &&iy) -> ConvertRealOperandsResult {
// Can happen in a CMPLX() constructor. Per F'2018,
// both integer operands are converted to default REAL.
- return std::optional{std::make_pair(
- ToCategoryExpr(ConvertToType<DefaultReal>(std::move(ix))),
- ToCategoryExpr(ConvertToType<DefaultReal>(std::move(iy))))};
+ return {ConversionHelper(ConvertToType<DefaultReal>(std::move(ix)),
+ ConvertToType<DefaultReal>(std::move(iy)))};
},
[&](Expr<SomeInteger> &&ix,
Expr<SomeReal> &&ry) -> ConvertRealOperandsResult {
- auto rx{ConvertTo(ry, std::move(ix))};
- return std::optional{std::make_pair(std::move(rx), std::move(ry))};
+ return {
+ ConversionHelper(ConvertTo(ry, std::move(ix)), std::move(ry))};
},
[&](Expr<SomeReal> &&rx,
Expr<SomeInteger> &&iy) -> ConvertRealOperandsResult {
- auto ry{ConvertTo(rx, std::move(iy))};
- return std::optional{std::make_pair(std::move(rx), std::move(ry))};
+ return {
+ ConversionHelper(std::move(rx), ConvertTo(rx, std::move(iy)))};
},
[&](Expr<SomeReal> &&rx,
Expr<SomeReal> &&ry) -> ConvertRealOperandsResult {
- ConvertToSameKind(rx, ry);
- return std::optional{std::make_pair(std::move(rx), std::move(ry))};
+ return {ConversionHelper(std::move(rx), std::move(ry))};
},
[&](auto &&, auto &&) -> ConvertRealOperandsResult {
// TODO: allow BOZ here?
}};
using fType = ConvertRealOperandsResult(Expr<SomeType> &&, Expr<SomeType> &&);
std::function<fType> f{partial};
- return common::JoinOptionals(
- common::MapOptional(f, std::move(x), std::move(y)));
+ return common::JoinOptional(
+ common::MapOptional(std::move(f), std::move(x), std::move(y)));
}
template<template<typename> class OPR, TypeCategory CAT>
#include "expression.h"
#include "../common/idioms.h"
#include "../parser/message.h"
+#include <array>
#include <optional>
#include <utility>
// Generalizers: these take expressions of more specific types and wrap
// them in more abstract containers.
+template<typename A> Expr<ResultType<A>> AsExpr(A &&x) {
+ return {std::move(x)};
+}
+
template<TypeCategory CAT, int KIND>
-Expr<SomeKind<CAT>> ToCategoryExpr(Expr<Type<CAT, KIND>> &&x) {
+Expr<SomeKind<CAT>> AsCategoryExpr(Expr<Type<CAT, KIND>> &&x) {
return {std::move(x)};
}
-template<typename A> Expr<SomeType> ToGenericExpr(A &&x) {
+template<typename A> Expr<SomeType> AsGenericExpr(A &&x) {
return {std::move(x)};
}
template<TypeCategory CAT, int KIND>
-Expr<SomeType> ToGenericExpr(Expr<Type<CAT, KIND>> &&x) {
- return {ToCategoryExpr(std::move(x))};
+Expr<SomeType> AsGenericExpr(Expr<Type<CAT, KIND>> &&x) {
+ return {AsCategoryExpr(std::move(x))};
}
// Creation of conversion expressions can be done to either a known
template<TypeCategory TC, int TK, TypeCategory FC, int FK>
Expr<Type<TC, TK>> ConvertTo(
const Expr<Type<TC, TK>> &, Expr<Type<FC, FK>> &&x) {
- return ConvertToType<Type<TC, TK>>(ToCategoryExpr(std::move(x)));
+ return ConvertToType<Type<TC, TK>>(AsCategoryExpr(std::move(x)));
}
template<TypeCategory TC, TypeCategory FC>
return std::visit(
[&](const auto &toKindExpr) {
using KindExpr = std::decay_t<decltype(toKindExpr)>;
- return ToCategoryExpr(
+ return AsCategoryExpr(
ConvertToType<ResultType<KindExpr>>(std::move(from)));
},
to.u);
template<TypeCategory TC, TypeCategory FC, int FK>
Expr<SomeKind<TC>> ConvertTo(
const Expr<SomeKind<TC>> &to, Expr<Type<FC, FK>> &&from) {
- return ConvertTo(to, ToCategoryExpr(std::move(from)));
+ return ConvertTo(to, AsCategoryExpr(std::move(from)));
}
template<typename FT>
Expr<SomeType> ConvertTo(const Expr<SomeType> &to, Expr<FT> &&from) {
return std::visit(
[&](const auto &toCatExpr) {
- return ToGenericExpr(ConvertTo(toCatExpr, std::move(from)));
+ return AsGenericExpr(ConvertTo(toCatExpr, std::move(from)));
},
to.u);
}
// Ensure that both operands of an intrinsic REAL operation (or CMPLX()
// constructor) are INTEGER or REAL, then convert them as necessary to the
// same kind of REAL.
-// TODO pmk: need a better type that guarantees that both have same kind
+template<int N = 2> struct SameExprsHelper {
+ template<typename A> using SameExprs = std::array<Expr<A>, N>;
+};
+template<typename A, int N = 2> using SameExprs = std::array<Expr<A>, N>;
+template<TypeCategory CAT, int N = 2>
+using SameKindExprs =
+ common::MapTemplate<SameExprsHelper<N>::template SameExprs,
+ CategoryTypes<CAT>>;
using ConvertRealOperandsResult =
- std::optional<std::pair<Expr<SomeReal>, Expr<SomeReal>>>;
+ std::optional<SameKindExprs<TypeCategory::Real, 2>>;
ConvertRealOperandsResult ConvertRealOperands(
parser::ContextualMessages &, Expr<SomeType> &&, Expr<SomeType> &&);
ConvertRealOperandsResult ConvertRealOperands(parser::ContextualMessages &,
return common::GetIf<Scalar<T>>(u);
}
- common::MapTemplate<Scalar, std::variant, Types> u;
+ common::MapTemplate<Scalar, Types> u;
};
template<TypeCategory CATEGORY>
if (kind == Ty::kind) {
result = Expr<Ty>{evaluate::Constant<Ty>{std::move(value)}};
} else {
- SetKindTraverser<J+1>(kind);
+ SetKindTraverser<J + 1>(kind);
}
}
}
return std::move(helper.result);
}
-template<typename A>
-MaybeExpr PackageGeneric(std::optional<A> &&x) {
+template<typename A> MaybeExpr PackageGeneric(std::optional<A> &&x) {
if (x.has_value()) {
- return {evaluate::ToGenericExpr(std::move(*x))};
+ return {evaluate::AsGenericExpr(std::move(*x))};
}
return std::nullopt;
}
std::optional<Expr<evaluate::SubscriptInteger>> lb, ub;
if (lbTree.has_value()) {
if (MaybeIntExpr lbExpr{AnalyzeHelper(ea, *lbTree)}) {
- lb = evaluate::ConvertToType<evaluate::SubscriptInteger>(std::move(*lbExpr));
+ lb = evaluate::ConvertToType<evaluate::SubscriptInteger>(
+ std::move(*lbExpr));
}
}
if (ubTree.has_value()) {
if (MaybeIntExpr ubExpr{AnalyzeHelper(ea, *ubTree)}) {
- ub = evaluate::ConvertToType<evaluate::SubscriptInteger>(std::move(*ubExpr));
+ ub = evaluate::ConvertToType<evaluate::SubscriptInteger>(
+ std::move(*ubExpr));
}
}
if (!lb.has_value() || !ub.has_value()) {
evaluate::CopyableIndirection<evaluate::Substring> ind{std::move(substring)};
Expr<evaluate::DefaultCharacter> chExpr{std::move(ind)};
chExpr.Fold(ea.context());
- return {evaluate::ToGenericExpr(chExpr)};
+ return {evaluate::AsGenericExpr(chExpr)};
}
// Common handling of parser::IntLiteralConstant and SignedIntLiteralConstant
auto kind{ea.Analyze(std::get<std::optional<parser::KindParam>>(x.t),
ea.defaultIntegerKind())};
auto value{std::get<0>(x.t)}; // std::(u)int64_t
- ConstantHelper<TypeCategory::Integer, decltype(value)> helper{std::move(value)};
+ ConstantHelper<TypeCategory::Integer, decltype(value)> helper{
+ std::move(value)};
helper.SetKind(kind);
if (!helper.result.has_value()) {
ea.context().messages.Say("unsupported INTEGER(KIND=%ju)"_err_en_US,
if (context.flushDenormalsToZero) {
value = value.FlushDenormalToZero();
}
- return {evaluate::ToCategoryExpr(Expr<RealType>{evaluate::Constant<RealType>{value}})};
+ return {evaluate::AsCategoryExpr(
+ Expr<RealType>{evaluate::Constant<RealType>{value}})};
}
struct RealHelper {
if (kind == Ty::kind) {
result = ReadRealLiteral<Ty::kind>(literal, context);
} else {
- SetKindTraverser<J+1>(kind);
+ SetKindTraverser<J + 1>(kind);
}
}
}
kindParam->u);
}
-// TODO pmk: need a way to represent a tuple of same-typed expressions, avoid CHECK here
std::optional<Expr<evaluate::SomeComplex>> ExpressionAnalyzer::ConstructComplex(
MaybeExpr &&real, MaybeExpr &&imaginary) {
if (auto converted{evaluate::ConvertRealOperands(
context_.messages, std::move(real), std::move(imaginary))}) {
return {std::visit(
- [&](auto &&re) -> Expr<evaluate::SomeComplex> {
- using realType = evaluate::ResultType<decltype(re)>;
- auto *im{std::get_if<Expr<realType>>(&converted->second.u)};
- CHECK(im != nullptr);
- constexpr int kind{realType::kind};
- using zType = evaluate::Type<TypeCategory::Complex, kind>;
- return {Expr<evaluate::SomeComplex>{Expr<zType>{evaluate::ComplexConstructor<kind>{
- std::move(re), std::move(*im)}}}};
+ [](auto &&pair) -> std::optional<Expr<evaluate::SomeComplex>> {
+ using realType = evaluate::ResultType<decltype(pair[0])>;
+ using zType = evaluate::SameKind<TypeCategory::Complex, realType>;
+ auto cmplx{evaluate::ComplexConstructor<zType::kind>{
+ std::move(pair[0]), std::move(pair[1])}};
+ return {evaluate::AsCategoryExpr(evaluate::AsExpr(std::move(cmplx)))};
},
- std::move(converted->first.u))};
+ *converted)};
}
return std::nullopt;
}