diff --git a/src/backports/concepts.h b/src/backports/concepts.h index 942466f23d..15a8897047 100644 --- a/src/backports/concepts.h +++ b/src/backports/concepts.h @@ -24,10 +24,16 @@ // `CPP_lambda(capture)(arg)(requires ...)`: Expands lambda to use // `requires` in C++20 mode and `std::enable_if_t` in C++17 mode. // +// `CPP_lambda_mut(capture)(arg)(requires ...)`: Same as +// `CPP_lambda` but for mutable lambdas. +// // `CPP_template_lambda(capture)(typenames...)(arg)(requires ...)`: Expands -// lambda with (C++20) explicit gemplate parameters to use +// lambda with (C++20) explicit template parameters to use // `requires` in C++20 mode and `std::enable_if_t` in C++17 mode. // +// `CPP_template_lambda_mut(capture)(typenames...)(arg)(requires ...)`: Same as +// `CPP_template_lambda` but for mutable lambdas. +// // Example usages: // // `QL_CONCEPT_OR_NOTHING(std::view) auto x = someFunction();` @@ -63,6 +69,8 @@ #define CPP_member_def CPP_member_def_sfinae #define CPP_lambda CPP_lambda_sfinae #define CPP_template_lambda CPP_template_lambda_sfinae +#define CPP_lambda_mut CPP_lambda_mut_sfinae +#define CPP_template_lambda_mut CPP_template_lambda_mut_sfinae #else #define QL_CONCEPT_OR_NOTHING(...) __VA_ARGS__ #define QL_CONCEPT_OR_TYPENAME(...) __VA_ARGS__ @@ -74,6 +82,8 @@ #define CPP_member_def CPP_member #define CPP_lambda CPP_LAMBDA_20 #define CPP_template_lambda CPP_TEMPLATE_LAMBDA_20 +#define CPP_lambda_mut CPP_lambda_mut_20 +#define CPP_template_lambda_mut CPP_TEMPLATE_LAMBDA_MUT_20 #endif // The namespace `ql::concepts` includes concepts that are contained in the diff --git a/src/backports/cppTemplate2.h b/src/backports/cppTemplate2.h index 171dc5dca4..830d9e7e21 100644 --- a/src/backports/cppTemplate2.h +++ b/src/backports/cppTemplate2.h @@ -63,12 +63,17 @@ #define CPP_TEMPLATE_LAMBDA_20(...) [__VA_ARGS__] CPP_TEMPLATE_LAMBDA_ARGS +#define CPP_TEMPLATE_LAMBDA_MUT_20(...) \ + [__VA_ARGS__] CPP_TEMPLATE_LAMBDA_MUT_ARGS + // The internals of the `CPP_lambda` template #define CPP_LAMBDA_SFINAE_ARGS(...) \ (__VA_ARGS__ CPP_LAMBDA_SFINAE_AUX_ #define CPP_LAMBDA_SFINAE_AUX_WHICH_(FIRST, ...) \ - CPP_PP_EVAL(CPP_PP_CHECK, CPP_PP_CAT(CPP_LAMBDA_SFINAE_PROBE_CONCEPT_, FIRST)) + CPP_PP_EVAL(CPP_PP_CHECK, FIRST) +// CPP_PP_EVAL(CPP_PP_CHECK, CPP_PP_CAT(CPP_LAMBDA_SFINAE_PROBE_CONCEPT_, +// FIRST)) #define CPP_LAMBDA_SFINAE_AUX_(...) \ CPP_PP_CAT(CPP_LAMBDA_SFINAE_AUX_, \ @@ -87,9 +92,15 @@ CPP_PP_CAT(CPP_LAMBDA_SFINAE_AUX_3_, __VA_ARGS__) \ #define CPP_TEMPLATE_LAMBDA_ARGS_sfinae(...) \ <__VA_ARGS__> CPP_LAMBDA_SFINAE_ARGS +#define CPP_TEMPLATE_LAMBDA_MUT_ARGS_sfinae(...) \ + <__VA_ARGS__> CPP_LAMBDA_MUT_SFINAE_ARGS + #define CPP_template_lambda_sfinae(...) \ [__VA_ARGS__] CPP_TEMPLATE_LAMBDA_ARGS_sfinae +#define CPP_template_lambda_mut_sfinae(...) \ + [__VA_ARGS__] CPP_TEMPLATE_LAMBDA_MUT_ARGS_sfinae + #define CPP_LAMBDA_SFINAE_AUX_3_requires #define CPP_LAMBDA_ARGS(...) (__VA_ARGS__) CPP_LAMBDA_AUX_ @@ -102,3 +113,20 @@ CPP_PP_CAT(CPP_LAMBDA_SFINAE_AUX_3_, __VA_ARGS__) \ #define CPP_LAMBDA_AUX_0(...) __VA_ARGS__ #define CPP_TEMPLATE_LAMBDA_ARGS(...) <__VA_ARGS__> CPP_LAMBDA_ARGS + +#define CPP_TEMPLATE_LAMBDA_MUT_ARGS(...) <__VA_ARGS__> CPP_LAMBDA_ARGS_MUT + +#define CPP_lambda_mut_sfinae(...) \ + CPP_PP_IGNORE_CXX2A_COMPAT_BEGIN \ + [__VA_ARGS__] CPP_LAMBDA_MUT_SFINAE_ARGS + +#define CPP_LAMBDA_MUT_SFINAE_ARGS(...) \ +(__VA_ARGS__ CPP_LAMBDA_MUT_SFINAE_AUX_ + +#define CPP_LAMBDA_MUT_SFINAE_AUX_(...) \ + CPP_PP_CAT(CPP_LAMBDA_SFINAE_AUX_, \ + CPP_LAMBDA_SFINAE_AUX_WHICH_(__VA_ARGS__, )) \ + (__VA_ARGS__) mutable + +#define CPP_LAMBDA_ARGS_MUT(...) (__VA_ARGS__) mutable CPP_LAMBDA_AUX_ +#define CPP_lambda_mut_20(...) [__VA_ARGS__] CPP_LAMBDA_ARGS_MUT diff --git a/src/engine/Bind.cpp b/src/engine/Bind.cpp index a0e7e78c9a..96e5d5edf6 100644 --- a/src/engine/Bind.cpp +++ b/src/engine/Bind.cpp @@ -161,8 +161,8 @@ IdTable Bind::computeExpressionBind( idTable.addEmptyColumn(); auto outputColumn = idTable.getColumn(idTable.numColumns() - 1); - auto visitor = [&]( - T&& singleResult) mutable { + auto visitor = CPP_template_lambda_mut(&)(typename T)(T && singleResult)( + requires sparqlExpression::SingleExpressionResult) { constexpr static bool isVariable = std::is_same_v; constexpr static bool isStrongId = std::is_same_v; @@ -180,8 +180,7 @@ IdTable Bind::computeExpressionBind( constexpr bool isConstant = sparqlExpression::isConstantResult; auto resultGenerator = sparqlExpression::detail::makeGenerator( - std::forward(singleResult), outputColumn.size(), - &evaluationContext); + AD_FWD(singleResult), outputColumn.size(), &evaluationContext); if constexpr (isConstant) { auto it = resultGenerator.begin(); diff --git a/src/engine/Filter.cpp b/src/engine/Filter.cpp index 4ac9bc5aea..fe5c6ad3e0 100644 --- a/src/engine/Filter.cpp +++ b/src/engine/Filter.cpp @@ -147,70 +147,67 @@ CPP_template_def(int WIDTH, typename Table)( // NOTE: the explicit (seemingly redundant) capture of `resultTable` is // required to work around a bug in Clang 17, see // https://github.com/llvm/llvm-project/issues/61267 - auto computeResult = - [this, &resultTable = resultTable, &input, &inputTable, - &dynamicResultTable, - &evaluationContext]( - T&& singleResult) { - if constexpr (std::is_same_v) { - AD_CONTRACT_CHECK(input.size() == evaluationContext.size()); - // If the expression result is given as a set of intervals, we copy - // the corresponding parts of `input` to `resultTable`. - // - // NOTE: One of the interval ends may be larger than `input.size()` - // (as the result of a negation). - auto totalSize = std::accumulate( - singleResult._intervals.begin(), singleResult._intervals.end(), - resultTable.size(), - [&input](const auto& sum, const auto& interval) { - size_t intervalBegin = interval.first; - size_t intervalEnd = std::min(interval.second, input.size()); - return sum + (intervalEnd - intervalBegin); - }); - if (resultTable.empty() && totalSize == inputTable.size()) { - // The binary filter contains all elements of the input, and we have - // no previous results, so we can simply copy or move the complete - // table. - dynamicResultTable = AD_FWD(inputTable).moveOrClone(); - return; - } - checkCancellation(); - for (auto [intervalBegin, intervalEnd] : singleResult._intervals) { - intervalEnd = std::min(intervalEnd, input.size()); - resultTable.insertAtEnd(inputTable, intervalBegin, intervalEnd); - checkCancellation(); - } - AD_CORRECTNESS_CHECK(resultTable.size() == totalSize); - } else { - // In the general case, we generate all expression results and apply - // the `EffectiveBooleanValueGetter` to each. - // - // NOTE: According to the standard, this means that values like zero, - // UNDEF, and empty strings are converted to `false` and hence the - // corresponding rows from `input` are filtered out. - // - // TODO Check whether it is feasible to precompute the - // number of `true` values and use that to reserve the right - // amount of space for `resultTable`, like we do it for the set of - // intervals above. This depends on how expensive the evaluation with - // the `EffectiveBooleanValueGetter` is. - auto resultGenerator = sparqlExpression::detail::makeGenerator( - std::forward(singleResult), input.size(), &evaluationContext); - size_t i = 0; - - using ValueGetter = - sparqlExpression::detail::EffectiveBooleanValueGetter; - ValueGetter valueGetter{}; - for (auto&& resultValue : resultGenerator) { - if (valueGetter(resultValue, &evaluationContext) == - ValueGetter::Result::True) { - resultTable.push_back(input[i]); - } - checkCancellation(); - ++i; - } + auto computeResult = CPP_template_lambda( + this, &resultTable = resultTable, &input, &inputTable, + &dynamicResultTable, &evaluationContext)(typename T)(T && singleResult)( + requires sparqlExpression::SingleExpressionResult) { + if constexpr (std::is_same_v) { + AD_CONTRACT_CHECK(input.size() == evaluationContext.size()); + // If the expression result is given as a set of intervals, we copy + // the corresponding parts of `input` to `resultTable`. + // + // NOTE: One of the interval ends may be larger than `input.size()` + // (as the result of a negation). + auto totalSize = std::accumulate( + singleResult._intervals.begin(), singleResult._intervals.end(), + resultTable.size(), [&input](const auto& sum, const auto& interval) { + size_t intervalBegin = interval.first; + size_t intervalEnd = std::min(interval.second, input.size()); + return sum + (intervalEnd - intervalBegin); + }); + if (resultTable.empty() && totalSize == inputTable.size()) { + // The binary filter contains all elements of the input, and we have + // no previous results, so we can simply copy or move the complete + // table. + dynamicResultTable = AD_FWD(inputTable).moveOrClone(); + return; + } + checkCancellation(); + for (auto [intervalBegin, intervalEnd] : singleResult._intervals) { + intervalEnd = std::min(intervalEnd, input.size()); + resultTable.insertAtEnd(inputTable, intervalBegin, intervalEnd); + checkCancellation(); + } + AD_CORRECTNESS_CHECK(resultTable.size() == totalSize); + } else { + // In the general case, we generate all expression results and apply + // the `EffectiveBooleanValueGetter` to each. + // + // NOTE: According to the standard, this means that values like zero, + // UNDEF, and empty strings are converted to `false` and hence the + // corresponding rows from `input` are filtered out. + // + // TODO Check whether it is feasible to precompute the + // number of `true` values and use that to reserve the right + // amount of space for `resultTable`, like we do it for the set of + // intervals above. This depends on how expensive the evaluation with + // the `EffectiveBooleanValueGetter` is. + auto resultGenerator = sparqlExpression::detail::makeGenerator( + AD_FWD(singleResult), input.size(), &evaluationContext); + size_t i = 0; + + using ValueGetter = sparqlExpression::detail::EffectiveBooleanValueGetter; + ValueGetter valueGetter{}; + for (auto&& resultValue : resultGenerator) { + if (valueGetter(resultValue, &evaluationContext) == + ValueGetter::Result::True) { + resultTable.push_back(input[i]); } - }; + checkCancellation(); + ++i; + } + } + }; std::visit(computeResult, std::move(expressionResult)); // Detect the case that we have directly written the `dynamicResultTable` diff --git a/src/engine/GroupBy.cpp b/src/engine/GroupBy.cpp index 6fdeca1833..5205e4723e 100644 --- a/src/engine/GroupBy.cpp +++ b/src/engine/GroupBy.cpp @@ -208,8 +208,8 @@ void GroupBy::processGroup( evaluationContext._previousResultsFromSameGroup.at(resultColumn) = sparqlExpression::copyExpressionResult(expressionResult); - auto visitor = [&]( - T&& singleResult) mutable { + auto visitor = CPP_template_lambda_mut(&)(typename T)(T && singleResult)( + requires sparqlExpression::SingleExpressionResult) { constexpr static bool isStrongId = std::is_same_v; AD_CONTRACT_CHECK(sparqlExpression::isConstantResult); if constexpr (isStrongId) { @@ -1097,12 +1097,11 @@ void GroupBy::extractValues( sparqlExpression::ExpressionResult&& expressionResult, sparqlExpression::EvaluationContext& evaluationContext, IdTable* resultTable, LocalVocab* localVocab, size_t outCol) { - auto visitor = [&evaluationContext, &resultTable, &localVocab, - &outCol]( - T&& singleResult) mutable { + auto visitor = CPP_template_lambda_mut(&evaluationContext, &resultTable, + &localVocab, &outCol)(typename T)( + T && singleResult)(requires sparqlExpression::SingleExpressionResult) { auto generator = sparqlExpression::detail::makeGenerator( - std::forward(singleResult), evaluationContext.size(), - &evaluationContext); + AD_FWD(singleResult), evaluationContext.size(), &evaluationContext); auto targetIterator = resultTable->getColumn(outCol).begin() + evaluationContext._beginIndex; @@ -1480,10 +1479,10 @@ static constexpr auto makeProcessGroupsVisitor = [](size_t blockSize, const sparqlExpression::EvaluationContext* evaluationContext, const std::vector& hashEntries) { - return [blockSize, evaluationContext, - &hashEntries]( - T&& singleResult, A& aggregationDataVector) { + return CPP_template_lambda(blockSize, evaluationContext, &hashEntries)( + typename T, typename A)(T && singleResult, A & aggregationDataVector)( + requires sparqlExpression::SingleExpressionResult && + VectorOfAggregationData) { auto generator = sparqlExpression::detail::makeGenerator( std::forward(singleResult), blockSize, evaluationContext); diff --git a/src/engine/LazyGroupBy.cpp b/src/engine/LazyGroupBy.cpp index 75d547983e..60a1249e69 100644 --- a/src/engine/LazyGroupBy.cpp +++ b/src/engine/LazyGroupBy.cpp @@ -73,11 +73,12 @@ void LazyGroupBy::processBlock( evaluationContext); visitAggregate( - [blockSize, - &evaluationContext]( - VectorOfAggregationData auto& aggregateData, T&& singleResult) { + CPP_template_lambda(blockSize, &evaluationContext)( + typename A, typename T)(A & aggregateData, T && singleResult)( + requires VectorOfAggregationData && + sparqlExpression::SingleExpressionResult) { auto generator = sparqlExpression::detail::makeGenerator( - std::forward(singleResult), blockSize, &evaluationContext); + AD_FWD(singleResult), blockSize, &evaluationContext); for (const auto& val : generator) { aggregateData.at(0).addValue(val, &evaluationContext); diff --git a/src/engine/sparqlExpressions/AggregateExpression.cpp b/src/engine/sparqlExpressions/AggregateExpression.cpp index 87ca749209..d1f4c9940c 100644 --- a/src/engine/sparqlExpressions/AggregateExpression.cpp +++ b/src/engine/sparqlExpressions/AggregateExpression.cpp @@ -15,11 +15,12 @@ namespace sparqlExpression::detail { // function. template struct EvaluateOnChildOperand { - ExpressionResult operator()(const AggregateOperation& aggregateOperation, - ValueId resultForEmptyGroup, - const FinalOperation& finalOperation, - EvaluationContext* context, bool distinct, - SingleExpressionResult auto&& operand) const { + template + auto operator()(const AggregateOperation& aggregateOperation, + ValueId resultForEmptyGroup, + const FinalOperation& finalOperation, + EvaluationContext* context, bool distinct, O&& operand) const + -> CPP_ret(ExpressionResult)(requires SingleExpressionResult) { // Perform the more efficient calculation on `SetOfInterval`s if it is // possible. // @@ -55,9 +56,9 @@ struct EvaluateOnChildOperand { // Helper lambda for aggregating two values. auto aggregateTwoValues = [&aggregateOperation, context]( auto&& x, auto&& y) -> decltype(auto) { - if constexpr (requires { - aggregateOperation._function(AD_FWD(x), AD_FWD(y)); - }) { + if constexpr (ranges::invocable) { return aggregateOperation._function(AD_FWD(x), AD_FWD(y)); } else { return aggregateOperation._function(AD_FWD(x), AD_FWD(y), context); @@ -117,7 +118,7 @@ struct EvaluateOnChildOperand { // // TODO Check if this is really necessary, or if we can also use // IDs in the intermediate steps without loss of efficiency. - if constexpr (requires { makeNumericId(result); }) { + if constexpr (ValueAsNumericId) { return makeNumericId(result); } else { return result; diff --git a/src/engine/sparqlExpressions/AggregateExpression.h b/src/engine/sparqlExpressions/AggregateExpression.h index 71a0a6b36f..b43827ddb3 100644 --- a/src/engine/sparqlExpressions/AggregateExpression.h +++ b/src/engine/sparqlExpressions/AggregateExpression.h @@ -104,7 +104,9 @@ using AGG_EXP = AggregateExpression< // with arguments and result of type `NumericValue` (which is a `std::variant`). template inline auto makeNumericExpressionForAggregate() { - return [](const std::same_as auto&... args) -> NumericValue { + return [](const Args&... args) + -> CPP_ret(NumericValue)( + requires(concepts::same_as&&...)) { auto visitor = [](const Ts&... t) -> NumericValue { if constexpr ((... || std::is_same_v)) { return NotNumeric{}; @@ -181,19 +183,19 @@ inline const auto compareIdsOrStrings = // Aggregate expression for MIN and MAX. template -inline const auto minMaxLambdaForAllTypes = - [](const T& a, const T& b, - const EvaluationContext* ctx) { - auto actualImpl = [ctx](const auto& x, const auto& y) { - return compareIdsOrStrings(x, y, ctx); - }; - if constexpr (ad_utility::isSimilar) { - return std::get(actualImpl(a, b)); - } else { - // TODO We should definitely move strings here. - return std::visit(actualImpl, a, b); - } - }; +inline const auto minMaxLambdaForAllTypes = CPP_template_lambda()(typename T)( + const T& a, const T& b, + const EvaluationContext* ctx)(requires SingleExpressionResult) { + auto actualImpl = [ctx](const auto& x, const auto& y) { + return compareIdsOrStrings(x, y, ctx); + }; + if constexpr (ad_utility::isSimilar) { + return std::get(actualImpl(a, b)); + } else { + // TODO We should definitely move strings here. + return std::visit(actualImpl, a, b); + } +}; constexpr inline auto minLambdaForAllTypes = minMaxLambdaForAllTypes; constexpr inline auto maxLambdaForAllTypes = diff --git a/src/engine/sparqlExpressions/ConditionalExpressions.cpp b/src/engine/sparqlExpressions/ConditionalExpressions.cpp index 29aec5a7b2..bc26000d20 100644 --- a/src/engine/sparqlExpressions/ConditionalExpressions.cpp +++ b/src/engine/sparqlExpressions/ConditionalExpressions.cpp @@ -12,10 +12,11 @@ namespace sparqlExpression { namespace detail::conditional_expressions { using namespace sparqlExpression::detail; [[maybe_unused]] auto ifImpl = - []( - EffectiveBooleanValueGetter::Result condition, T&& i, - U&& e) -> IdOrLiteralOrIri requires std::is_rvalue_reference_v && - std::is_rvalue_reference_v { + [](EffectiveBooleanValueGetter::Result condition, + T&& i, U&& e) + -> CPP_ret(IdOrLiteralOrIri)( + requires SingleExpressionResult&& SingleExpressionResult&& + std::is_rvalue_reference_v&& std::is_rvalue_reference_v) { if (condition == EffectiveBooleanValueGetter::Result::True) { return AD_FWD(i); } else { @@ -65,9 +66,9 @@ class CoalesceExpression : public VariadicExpression { }; auto visitConstantExpressionResult = - [&nextUnboundIndices, &unboundIndices, &isUnbound, &result, - ctx ](T && childResult) - requires isConstantResult { + CPP_template_lambda(&nextUnboundIndices, &unboundIndices, &isUnbound, + &result, ctx)(typename T)(T && childResult)( + requires SingleExpressionResult && isConstantResult) { IdOrLiteralOrIri constantResult{AD_FWD(childResult)}; if (isUnbound(constantResult)) { nextUnboundIndices = std::move(unboundIndices); @@ -92,10 +93,10 @@ class CoalesceExpression : public VariadicExpression { // result so far is unbound, and the child result is bound. While doing so, // set up the `nextUnboundIndices` vector for the next step. auto visitVectorExpressionResult = - [&result, &unboundIndices, &nextUnboundIndices, &ctx, & - isUnbound ](T && childResult) - requires std::is_rvalue_reference_v { - static_assert(!isConstantResult); + CPP_template_lambda(&result, &unboundIndices, &nextUnboundIndices, &ctx, + &isUnbound)(typename T)(T && childResult)( + requires CPP_NOT(isConstantResult && SingleExpressionResult && + std::is_rvalue_reference_v)) { auto gen = detail::makeGenerator(AD_FWD(childResult), ctx->size(), ctx); // Iterator to the next index where the result so far is unbound. auto unboundIdxIt = unboundIndices.begin(); @@ -125,11 +126,10 @@ class CoalesceExpression : public VariadicExpression { }, [ctx]() { ctx->cancellationHandle_->throwIfCancelled(); }); }; - auto visitExpressionResult = - [ - &visitConstantExpressionResult, &visitVectorExpressionResult - ](T && childResult) - requires std::is_rvalue_reference_v { + auto visitExpressionResult = CPP_template_lambda( + &visitConstantExpressionResult, + &visitVectorExpressionResult)(typename T)(T && childResult)( + requires SingleExpressionResult && std::is_rvalue_reference_v) { // If the previous expression result is a constant, we can skip the // loop. if constexpr (isConstantResult) { diff --git a/src/engine/sparqlExpressions/ConvertToNumericExpression.cpp b/src/engine/sparqlExpressions/ConvertToNumericExpression.cpp index 5aa0535b2e..710d0f5e02 100644 --- a/src/engine/sparqlExpressions/ConvertToNumericExpression.cpp +++ b/src/engine/sparqlExpressions/ConvertToNumericExpression.cpp @@ -12,9 +12,9 @@ namespace detail::to_numeric { // class that converts an input `int64_t`, `double` or `std::string` // to a numeric value `int64_t` or `double` -template -requires std::same_as || std::same_as -class ToNumericImpl { +CPP_template(typename T, bool AllowExponentialNotation = true)( + requires(concepts::same_as || + concepts::same_as)) class ToNumericImpl { private: Id getFromString(const std::string& input) const { auto str = absl::StripAsciiWhitespace(input); @@ -45,8 +45,9 @@ class ToNumericImpl { // ___________________________________________________________________________ template - requires std::integral || std::floating_point - Id getFromNumber(N number) const { + auto getFromNumber(N number) const + -> CPP_ret(Id)(requires(concepts::integral || + ad_utility::FloatingPoint)) { auto resNumber = static_cast(number); if constexpr (std::is_same_v) { return Id::makeFromInt(resNumber); diff --git a/src/engine/sparqlExpressions/GroupConcatExpression.h b/src/engine/sparqlExpressions/GroupConcatExpression.h index 59ba8fd605..f0caeab47d 100644 --- a/src/engine/sparqlExpressions/GroupConcatExpression.h +++ b/src/engine/sparqlExpressions/GroupConcatExpression.h @@ -26,8 +26,9 @@ class GroupConcatExpression : public SparqlExpression { // __________________________________________________________________________ ExpressionResult evaluate(EvaluationContext* context) const override { - auto impl = - [this, context](SingleExpressionResult auto&& el) -> ExpressionResult { + auto impl = [this, context](auto&& el) + -> CPP_ret(ExpressionResult)( + requires SingleExpressionResult) { std::string result; auto groupConcatImpl = [this, &result, context](auto generator) { // TODO Make this a configurable constant. diff --git a/src/engine/sparqlExpressions/LangExpression.cpp b/src/engine/sparqlExpressions/LangExpression.cpp index d5266e54ec..49dc55de2c 100644 --- a/src/engine/sparqlExpressions/LangExpression.cpp +++ b/src/engine/sparqlExpressions/LangExpression.cpp @@ -22,9 +22,9 @@ using OptValue = std::optional; // usage within the `Filter()`. In addition, `Filter()` requires access to the // optional underlying variable, this access is solved over the stand alone // function `getVariableFromLangExpression()`. -template -requires(isOperation) -class LangExpressionImpl : public NaryExpression { +CPP_template(typename NaryOperation)( + requires isOperation) class LangExpressionImpl + : public NaryExpression { public: using NaryExpression::NaryExpression; diff --git a/src/engine/sparqlExpressions/NaryExpression.h b/src/engine/sparqlExpressions/NaryExpression.h index 9f3b2c9f18..161090e1ee 100644 --- a/src/engine/sparqlExpressions/NaryExpression.h +++ b/src/engine/sparqlExpressions/NaryExpression.h @@ -6,9 +6,9 @@ #pragma once #include -#include #include +#include "backports/concepts.h" #include "engine/sparqlExpressions/SparqlExpression.h" // Factory functions for all kinds of expressions that only have other @@ -136,12 +136,13 @@ SparqlExpression::Ptr makeBoundExpression(SparqlExpression::Ptr child); // `make...` functions for n-ary expressions that have a compile-time known // number of children. This makes the testing easier, as we then can use the // same test helpers for all expressions. -template -requires std::is_invocable_r_v> -constexpr auto variadicExpressionFactory = - []... Exps>( - std::unique_ptr... children) { +CPP_template(auto function)( + requires std::is_invocable_r_v< + SparqlExpression::Ptr, decltype(function), + std::vector< + SparqlExpression::Ptr>>) constexpr auto variadicExpressionFactory = + [](std::unique_ptr... children) { + CPP_assert((ranges::derived_from && ...)); std::vector vec; (..., (vec.push_back(std::move(children)))); return std::invoke(function, std::move(vec)); diff --git a/src/engine/sparqlExpressions/NaryExpressionImpl.h b/src/engine/sparqlExpressions/NaryExpressionImpl.h index a284c1f21d..3c1d8270a0 100644 --- a/src/engine/sparqlExpressions/NaryExpressionImpl.h +++ b/src/engine/sparqlExpressions/NaryExpressionImpl.h @@ -12,8 +12,9 @@ namespace sparqlExpression::detail { template -requires(isOperation) class NaryExpression : public SparqlExpression { + CPP_assert(isOperation); + public: static constexpr size_t N = NaryOperation::N; using Children = std::array; @@ -27,9 +28,9 @@ class NaryExpression : public SparqlExpression { // Construct from `N` child expressions. Each of the children must have a type // `std::unique_ptr`. - explicit NaryExpression( - std::convertible_to auto... children) - requires(sizeof...(children) == N) + CPP_template(typename... C)( + requires(concepts::convertible_to&&...) + CPP_and(sizeof...(C) == N)) explicit NaryExpression(C... children) : NaryExpression{Children{std::move(children)...}} {} // __________________________________________________________________________ @@ -49,10 +50,11 @@ class NaryExpression : public SparqlExpression { std::span childrenImpl() override; // Evaluate the `naryOperation` on the `operands` using the `context`. - template - static ExpressionResult evaluateOnChildrenOperands( - NaryOperation naryOperation, EvaluationContext* context, - Operands&&... operands) { + CPP_template(typename... Operands)( + requires(SingleExpressionResult&&...)) static ExpressionResult + evaluateOnChildrenOperands(NaryOperation naryOperation, + EvaluationContext* context, + Operands&&... operands) { // Perform a more efficient calculation if a specialized function exists // that matches all operands. if (isAnySpecializedFunctionPossible(naryOperation._specializedFunctions, @@ -109,7 +111,9 @@ struct NumericIdWrapper { // `NumericValue` variant. template inline auto makeNumericExpression() { - return [](const std::same_as auto&... args) { + return [](const auto&... args) { + CPP_assert( + (concepts::same_as, NumericValue> && ...)); auto visitor = [](const Ts&... t) { if constexpr ((... || std::is_same_v)) { return Id::makeUndefined(); @@ -142,14 +146,12 @@ using TernaryBool = EffectiveBooleanValueGetter::Result; // _____________________________________________________________________________ template -requires(isOperation) NaryExpression::NaryExpression(Children&& children) : children_{std::move(children)} {} // _____________________________________________________________________________ template -requires(isOperation) ExpressionResult NaryExpression::evaluate( EvaluationContext* context) const { auto resultsOfChildren = ad_utility::applyFunctionToEachElementOfTuple( @@ -172,14 +174,13 @@ ExpressionResult NaryExpression::evaluate( // _____________________________________________________________________________ template -requires(isOperation) std::span NaryExpression::childrenImpl() { return {children_.data(), children_.size()}; } // __________________________________________________________________________ template -requires(isOperation) [[nodiscard]] string NaryExpression::getCacheKey( +[[nodiscard]] string NaryExpression::getCacheKey( const VariableToColumnMap& varColMap) const { string key = typeid(*this).name(); key += ad_utility::lazyStrJoin( diff --git a/src/engine/sparqlExpressions/NumericBinaryExpressions.cpp b/src/engine/sparqlExpressions/NumericBinaryExpressions.cpp index 173ed08271..2b3f5326b8 100644 --- a/src/engine/sparqlExpressions/NumericBinaryExpressions.cpp +++ b/src/engine/sparqlExpressions/NumericBinaryExpressions.cpp @@ -314,9 +314,9 @@ constexpr auto getMergeFunction(bool isNegated) { } // namespace //______________________________________________________________________________ -template -requires isOperation -class LogicalBinaryExpressionImpl : public NaryExpression { +CPP_template(typename BinaryPrefilterExpr, typename NaryOperation)( + requires isOperation) class LogicalBinaryExpressionImpl + : public NaryExpression { public: using NaryExpression::NaryExpression; diff --git a/src/engine/sparqlExpressions/NumericUnaryExpressions.cpp b/src/engine/sparqlExpressions/NumericUnaryExpressions.cpp index c97e7ce57e..de70bd8485 100644 --- a/src/engine/sparqlExpressions/NumericUnaryExpressions.cpp +++ b/src/engine/sparqlExpressions/NumericUnaryExpressions.cpp @@ -24,9 +24,9 @@ inline auto unaryNegate = [](TernaryBool a) { AD_FAIL(); }; -template -requires(isOperation) -class UnaryNegateExpressionImpl : public NaryExpression { +CPP_template(typename NaryOperation)( + requires isOperation) class UnaryNegateExpressionImpl + : public NaryExpression { public: using NaryExpression::NaryExpression; @@ -95,7 +95,7 @@ NARY_EXPRESSION(AbsExpression, 1, FV); // Rounding. inline const auto roundImpl = [](T num) { - if constexpr (std::is_floating_point_v) { + if constexpr (ad_utility::FloatingPoint) { auto res = std::round(num); // In SPARQL, negative numbers are rounded towards zero if they lie exactly // between two integers. @@ -110,7 +110,7 @@ NARY_EXPRESSION(RoundExpression, 1, FV); // Ceiling. inline const auto ceilImpl = [](T num) { - if constexpr (std::is_floating_point_v) { + if constexpr (ad_utility::FloatingPoint) { return std::ceil(num); } else { return num; @@ -121,7 +121,7 @@ NARY_EXPRESSION(CeilExpression, 1, FV); // Flooring. inline const auto floorImpl = [](T num) { - if constexpr (std::is_floating_point_v) { + if constexpr (ad_utility::FloatingPoint) { return std::floor(num); } else { return num; diff --git a/src/engine/sparqlExpressions/RegexExpression.cpp b/src/engine/sparqlExpressions/RegexExpression.cpp index 02047297ad..84b1e4c4e3 100644 --- a/src/engine/sparqlExpressions/RegexExpression.cpp +++ b/src/engine/sparqlExpressions/RegexExpression.cpp @@ -257,9 +257,9 @@ ExpressionResult RegexExpression::evaluatePrefixRegex( } // ___________________________________________________________________________ -template -ExpressionResult RegexExpression::evaluateGeneralCase( - T&& input, sparqlExpression::EvaluationContext* context) const { +CPP_template_def(typename T)(requires SingleExpressionResult) + ExpressionResult RegexExpression::evaluateGeneralCase( + T&& input, sparqlExpression::EvaluationContext* context) const { // We have one result for each row of the input. auto resultSize = context->size(); VectorWithMemoryLimit result{context->_allocator}; diff --git a/src/engine/sparqlExpressions/RegexExpression.h b/src/engine/sparqlExpressions/RegexExpression.h index cbf95c7f38..ec5e25bfa9 100644 --- a/src/engine/sparqlExpressions/RegexExpression.h +++ b/src/engine/sparqlExpressions/RegexExpression.h @@ -60,9 +60,9 @@ class RegexExpression : public SparqlExpression { sparqlExpression::EvaluationContext* context) const; // Evaluate for the general case. - template - ExpressionResult evaluateGeneralCase( - T&& input, sparqlExpression::EvaluationContext* context) const; + CPP_template(typename T)(requires SingleExpressionResult) ExpressionResult + evaluateGeneralCase(T&& input, + sparqlExpression::EvaluationContext* context) const; // Check if the `CancellationHandle` of `context` has been cancelled and throw // an exception if this is the case. diff --git a/src/engine/sparqlExpressions/RelationalExpressionHelpers.h b/src/engine/sparqlExpressions/RelationalExpressionHelpers.h index 62c5a1c430..088c47ae96 100644 --- a/src/engine/sparqlExpressions/RelationalExpressionHelpers.h +++ b/src/engine/sparqlExpressions/RelationalExpressionHelpers.h @@ -30,20 +30,20 @@ using ValueType = // Concept that requires that `T` logically stores numeric values. template -concept StoresNumeric = - std::integral> || std::floating_point>; +CPP_concept StoresNumeric = + concepts::integral> || ad_utility::FloatingPoint>; // Concept that requires that `T` logically stores `std::string`s. template -concept StoresStrings = ad_utility::SimilarTo, std::string>; +CPP_concept StoresStrings = ad_utility::SimilarTo, std::string>; // Concept that requires that `T` logically stores boolean values. template -concept StoresBoolean = std::is_same_v; +CPP_concept StoresBoolean = std::is_same_v; // Concept that requires that `T` logically stores `ValueId`s. template -concept StoresValueId = +CPP_concept StoresValueId = ad_utility::SimilarTo || ad_utility::SimilarTo> || ad_utility::SimilarTo; @@ -51,17 +51,18 @@ concept StoresValueId = // When `A` and `B` are `AreIncomparable`, comparisons between them will always // yield `not equal`, independent of the concrete values. template -concept AreIncomparable = (StoresNumeric && StoresStrings) || - (StoresNumeric && StoresStrings); +CPP_concept AreIncomparable = (StoresNumeric && StoresStrings) || + (StoresNumeric && StoresStrings); // True iff any of `A, B` is `StoresBoolean` (see above). template -concept AtLeastOneIsBoolean = StoresBoolean || StoresBoolean; +CPP_concept AtLeastOneIsBoolean = StoresBoolean || StoresBoolean; // The types for which comparisons like `<` are supported and not always false. template -concept AreComparable = !AtLeastOneIsBoolean && !AreIncomparable && - (StoresValueId || !StoresValueId); +CPP_concept AreComparable = + !AtLeastOneIsBoolean && !AreIncomparable && + (StoresValueId || !StoresValueId); // Apply the given `Comparison` to `a` and `b`. For example, if the `Comparison` // is `LT`, returns `a < b`. Note that the second template argument `Dummy` is @@ -129,7 +130,7 @@ inline std::pair getRangeFromVocab( // A concept for various types that either represent a string, an ID or a // consecutive range of IDs. For its usage see below. template -concept StoresStringOrId = +CPP_concept StoresStringOrId = ad_utility::SimilarToAny>; // Convert a string or `IdOrLiteralOrIri` value into the (possibly empty) range @@ -137,8 +138,8 @@ concept StoresStringOrId = // `getRangeFromVocab` above for details). This function also takes `ValueId`s // and `pair` which are simply returned unchanged. This makes // the usage of this function easier. -template -auto makeValueId(const S& value, const EvaluationContext* context) { +CPP_template(typename S)(requires StoresStringOrId) auto makeValueId( + const S& value, const EvaluationContext* context) { if constexpr (ad_utility::SimilarToAny>) { return value; } else if constexpr (ad_utility::isSimilar) { @@ -162,31 +163,37 @@ template inline const auto compareIdsOrStrings = - []( - const T& a, const U& b, - const EvaluationContext* ctx) -> valueIdComparators::ComparisonResult { + [](const T& a, const U& b, + const EvaluationContext* ctx) + -> CPP_ret(valueIdComparators::ComparisonResult)( + requires StoresStringOrId&& StoresStringOrId) { if constexpr (ad_utility::isSimilar && ad_utility::isSimilar) { return valueIdComparators::fromBool(applyComparison(a, b)); } else { auto x = makeValueId(a, ctx); auto y = makeValueId(b, ctx); - if constexpr (requires { valueIdComparators::compareIds(x, y, Comp); }) { + if constexpr (ranges::invocable), + decltype(x), decltype(y), + valueIdComparators::Comparison>) { // Compare two `ValueId`s return valueIdComparators::compareIds( x, y, Comp); - } else if constexpr (requires { - valueIdComparators::compareWithEqualIds( - x, y.first, y.second, Comp); - }) { + } else if constexpr (ranges::invocable< + decltype(valueIdComparators::compareWithEqualIds< + comparisonForIncompatibleTypes>), + decltype(x), decltype(y.first), decltype(y.second), + valueIdComparators::Comparison>) { // Compare `ValueId` with range of equal `ValueId`s (used when `value2` // is `string` or `vector`. return valueIdComparators::compareWithEqualIds< comparisonForIncompatibleTypes>(x, y.first, y.second, Comp); - } else if constexpr (requires { - valueIdComparators::compareWithEqualIds( - y, x.first, x.second, Comp); - }) { + } else if constexpr (ranges::invocable< + decltype(valueIdComparators::compareWithEqualIds< + comparisonForIncompatibleTypes>), + decltype(y), decltype(x.first), decltype(x.second), + valueIdComparators::Comparison>) { // Compare `ValueId` with range of equal `ValueId`s (used when `value2` // is `string` or `vector`. return valueIdComparators::compareWithEqualIds< diff --git a/src/engine/sparqlExpressions/RelationalExpressions.cpp b/src/engine/sparqlExpressions/RelationalExpressions.cpp index 96c8a106d9..6924fab3eb 100644 --- a/src/engine/sparqlExpressions/RelationalExpressions.cpp +++ b/src/engine/sparqlExpressions/RelationalExpressions.cpp @@ -32,8 +32,9 @@ using valueIdComparators::Comparison; // First the `idGenerator` for constants (string, int, double). It yields the // same ID `targetSize` many times. -template -requires isConstantResult auto idGenerator(const S& value, size_t targetSize, +CPP_template(typename S)( + requires SingleExpressionResult CPP_and + isConstantResult) auto idGenerator(const S& value, size_t targetSize, const EvaluationContext* context) -> cppcoro::generator { auto id = makeValueId(value, context); @@ -45,8 +46,9 @@ requires isConstantResult auto idGenerator(const S& value, size_t targetSize, // Version of `idGenerator` for vectors. Asserts that the size of the vector is // equal to `targetSize` and the yields the corresponding ID for each of the // elements in the vector. -template -requires isVectorResult auto idGenerator(const S& values, size_t targetSize, +CPP_template(typename S)( + requires SingleExpressionResult CPP_and + isVectorResult) auto idGenerator(const S& values, size_t targetSize, const EvaluationContext* context) -> cppcoro::generator { AD_CONTRACT_CHECK(targetSize == values.size()); @@ -75,9 +77,10 @@ CPP_template(typename S)( // `ValueId, vector, Variable`), then `idGenerator`s are returned for // both inputs. Else the "plain" generators from `sparqlExpression::detail` are // returned. These simply yield the values unchanged. -template -auto getGenerators(S1&& value1, S2&& value2, size_t targetSize, - const EvaluationContext* context) { +CPP_template(typename S1, typename S2)( + requires SingleExpressionResult CPP_and SingleExpressionResult< + S2>) auto getGenerators(S1&& value1, S2&& value2, size_t targetSize, + const EvaluationContext* context) { if constexpr (StoresValueId || StoresValueId) { return std::pair{idGenerator(AD_FWD(value1), targetSize, context), idGenerator(AD_FWD(value2), targetSize, context)}; @@ -139,9 +142,11 @@ ad_utility::SetOfIntervals evaluateWithBinarySearch( // The actual comparison function for the `SingleExpressionResult`'s which are // `AreComparable` (see above), which means that the comparison between them is // supported and not always false. -template -requires AreComparable ExpressionResult evaluateRelationalExpression( - S1&& value1, S2&& value2, const EvaluationContext* context) { +CPP_template(Comparison Comp, typename S1, typename S2)( + requires SingleExpressionResult CPP_and SingleExpressionResult + CPP_and AreComparable) ExpressionResult + evaluateRelationalExpression(S1&& value1, S2&& value2, + const EvaluationContext* context) { auto resultSize = sparqlExpression::detail::getResultSize(*context, value1, value2); constexpr static bool resultIsConstant = @@ -215,17 +220,17 @@ requires AreComparable ExpressionResult evaluateRelationalExpression( // The relational comparisons like `less than` are not useful for booleans and // thus currently throw an exception. -template -Id evaluateRelationalExpression(const A&, const B&, const EvaluationContext*) - requires StoresBoolean || StoresBoolean { +CPP_template(Comparison, typename A, + typename B)(requires(StoresBoolean || StoresBoolean)) Id + evaluateRelationalExpression(const A&, const B&, const EvaluationContext*) { throw std::runtime_error( "Relational expressions like <, >, == are currently not supported for " "boolean arguments"); } -template -requires AreIncomparable -Id evaluateRelationalExpression(const A&, const B&, const EvaluationContext*) { +CPP_template(Comparison Comp, typename A, + typename B)(requires AreIncomparable) Id + evaluateRelationalExpression(const A&, const B&, const EvaluationContext*) { // TODO We should probably return `undefined` here. if constexpr (Comp == Comparison::NE) { return Id::makeFromBool(true); @@ -236,10 +241,12 @@ Id evaluateRelationalExpression(const A&, const B&, const EvaluationContext*) { // For comparisons where exactly one of the operands is a variable, the variable // must come first. -template -requires(!AreComparable && AreComparable) -ExpressionResult evaluateRelationalExpression( - A&& a, B&& b, const EvaluationContext* context) { +CPP_template(Comparison Comp, typename A, typename B)( + requires SingleExpressionResult CPP_and SingleExpressionResult CPP_and + CPP_NOT(AreComparable) + CPP_and AreComparable) ExpressionResult + evaluateRelationalExpression(A&& a, B&& b, + const EvaluationContext* context) { return evaluateRelationalExpression( AD_FWD(b), AD_FWD(a), context); } diff --git a/src/engine/sparqlExpressions/SparqlExpressionGenerators.h b/src/engine/sparqlExpressions/SparqlExpressionGenerators.h index d7209cacab..3f317bb384 100644 --- a/src/engine/sparqlExpressions/SparqlExpressionGenerators.h +++ b/src/engine/sparqlExpressions/SparqlExpressionGenerators.h @@ -12,9 +12,9 @@ namespace sparqlExpression::detail { /// Convert a variable to a vector of all the Ids it is bound to in the /// `context`. -inline std::span getIdsFromVariable(const ::Variable& variable, - const EvaluationContext* context, - size_t beginIndex, size_t endIndex) { +inline std::span getIdsFromVariable( + const ::Variable& variable, const EvaluationContext* context, + size_t beginIndex, size_t endIndex) { const auto& inputTable = context->_inputTable; const auto& varToColMap = context->_variableToColumnMap; @@ -25,25 +25,31 @@ inline std::span getIdsFromVariable(const ::Variable& variable, std::span completeColumn = inputTable.getColumn(columnIndex); - AD_CONTRACT_CHECK(beginIndex <= endIndex && endIndex <= completeColumn.size()); - return {completeColumn.begin() + beginIndex, completeColumn.begin() + endIndex}; + AD_CONTRACT_CHECK(beginIndex <= endIndex && + endIndex <= completeColumn.size()); + return {completeColumn.begin() + beginIndex, + completeColumn.begin() + endIndex}; } // Overload that reads the `beginIndex` and the `endIndex` directly from the // `context -inline std::span getIdsFromVariable(const ::Variable& variable, - const EvaluationContext* context) { - return getIdsFromVariable(variable, context, context->_beginIndex, context->_endIndex); +inline std::span getIdsFromVariable( + const ::Variable& variable, const EvaluationContext* context) { + return getIdsFromVariable(variable, context, context->_beginIndex, + context->_endIndex); } /// Generators that yield `numItems` items for the various /// `SingleExpressionResult`s after applying a `Transformation` to them. /// Typically, this transformation is one of the value getters from /// `SparqlExpressionValueGetters` with an already bound `EvaluationContext`. -template -requires isConstantResult && std::invocable -cppcoro::generator>> resultGenerator( - T constant, size_t numItems, Transformation transformation = {}) { +CPP_template(typename T, typename Transformation = std::identity)( + requires SingleExpressionResult CPP_and isConstantResult CPP_and + ranges::invocable) + cppcoro::generator>> resultGenerator(T constant, size_t numItems, + Transformation transformation = + {}) { auto transformed = transformation(constant); for (size_t i = 0; i < numItems; ++i) { co_yield transformed; @@ -51,14 +57,17 @@ cppcoro::generator>> } CPP_template(typename T, typename Transformation = std::identity)( - requires ql::ranges::input_range) auto resultGenerator(T&& vector, size_t numItems, - Transformation transformation = {}) { + requires ql::ranges::input_range< + T>) auto resultGenerator(T&& vector, size_t numItems, + Transformation transformation = {}) { AD_CONTRACT_CHECK(numItems == vector.size()); - return ad_utility::allView(AD_FWD(vector)) | ql::views::transform(std::move(transformation)); + return ad_utility::allView(AD_FWD(vector)) | + ql::views::transform(std::move(transformation)); } template -inline cppcoro::generator>> +inline cppcoro::generator< + const std::decay_t>> resultGenerator(ad_utility::SetOfIntervals set, size_t targetSize, Transformation transformation = {}) { size_t i = 0; @@ -81,11 +90,14 @@ resultGenerator(ad_utility::SetOfIntervals set, size_t targetSize, /// Return a generator that yields `numItems` many items for the various /// `SingleExpressionResult` -template -auto makeGenerator(Input&& input, size_t numItems, const EvaluationContext* context, - Transformation transformation = {}) { +CPP_template(typename Input, typename Transformation = std::identity)( + requires SingleExpressionResult< + Input>) auto makeGenerator(Input&& input, size_t numItems, + const EvaluationContext* context, + Transformation transformation = {}) { if constexpr (ad_utility::isSimilar<::Variable, Input>) { - return resultGenerator(getIdsFromVariable(AD_FWD(input), context), numItems, transformation); + return resultGenerator(getIdsFromVariable(AD_FWD(input), context), numItems, + transformation); } else { return resultGenerator(AD_FWD(input), numItems, transformation); } @@ -93,24 +105,28 @@ auto makeGenerator(Input&& input, size_t numItems, const EvaluationContext* cont /// Generate `numItems` many values from the `input` and apply the /// `valueGetter` to each of the values. -inline auto valueGetterGenerator = []( - size_t numElements, EvaluationContext* context, - Input&& input, ValueGetter&& valueGetter) { - auto transformation = [ context, valueGetter ](I && i) - requires std::invocable { +inline auto valueGetterGenerator = + CPP_template_lambda()(typename ValueGetter, typename Input)( + size_t numElements, EvaluationContext* context, Input&& input, + ValueGetter&& valueGetter)(requires SingleExpressionResult) { + auto transformation = + CPP_template_lambda(context, valueGetter)(typename I)(I && i)( + requires ranges::invocable) { context->cancellationHandle_->throwIfCancelled(); return valueGetter(AD_FWD(i), context); }; - return makeGenerator(std::forward(input), numElements, context, transformation); + + return makeGenerator(AD_FWD(input), numElements, context, transformation); }; /// Do the following `numItems` times: Obtain the next elements e_1, ..., e_n /// from the `generators` and yield `function(e_1, ..., e_n)`, also as a /// generator. inline auto applyFunction = []( - Function&& function, size_t numItems, Generators... generators) - -> cppcoro::generator< - std::invoke_result_t...>> { + Function&& function, size_t numItems, + Generators... generators) + -> cppcoro::generator...>> { // A tuple holding one iterator to each of the generators. std::tuple iterators{generators.begin()...}; @@ -128,9 +144,11 @@ inline auto applyFunction = []( /// Return a generator that returns the `numElements` many results of the /// `Operation` applied to the `operands` -template -auto applyOperation(size_t numElements, Operation&&, EvaluationContext* context, - Operands&&... operands) { +CPP_template(typename Operation, typename... Operands)(requires( + SingleExpressionResult< + Operands>&&...)) auto applyOperation(size_t numElements, Operation&&, + EvaluationContext* context, + Operands&&... operands) { using ValueGetters = typename std::decay_t::ValueGetters; using Function = typename std::decay_t::Function; static_assert(std::tuple_size_v == sizeof...(Operands)); @@ -141,7 +159,8 @@ auto applyOperation(size_t numElements, Operation&&, EvaluationContext* context, // Function that takes all the generators as a parameter pack and computes the // generator for the operation result; - auto getResultFromGenerators = std::bind_front(applyFunction, Function{}, numElements); + auto getResultFromGenerators = + std::bind_front(applyFunction, Function{}, numElements); /// The `ValueGetters` are stored in a `std::tuple`, so we have to extract /// them via `std::apply`. First set up a lambda that performs the actual @@ -150,7 +169,8 @@ auto applyOperation(size_t numElements, Operation&&, EvaluationContext* context, // Both `operands` and `valueGetters` are parameter packs of equal size, // so there will be one call to `getValue` for each pair of // (`operands`, `valueGetter`) - return getResultFromGenerators(getValue(std::forward(operands), valueGetters)...); + return getResultFromGenerators( + getValue(std::forward(operands), valueGetters)...); }; return std::apply(getResultFromValueGetters, ValueGetters{}); @@ -168,10 +188,12 @@ inline auto makeStringResultGetter(LocalVocab* localVocab) { // Return the `Id` if the passed `value` contains one, alternatively add the // literal or iri in the `value` to the `localVocab` and return the newly // created `Id` instead. -inline Id idOrLiteralOrIriToId(const IdOrLiteralOrIri& value, LocalVocab* localVocab) { - return std::visit(ad_utility::OverloadCallOperator{[](ValueId id) { return id; }, - makeStringResultGetter(localVocab)}, - value); +inline Id idOrLiteralOrIriToId(const IdOrLiteralOrIri& value, + LocalVocab* localVocab) { + return std::visit( + ad_utility::OverloadCallOperator{[](ValueId id) { return id; }, + makeStringResultGetter(localVocab)}, + value); } } // namespace sparqlExpression::detail diff --git a/src/engine/sparqlExpressions/SparqlExpressionTypes.cpp b/src/engine/sparqlExpressions/SparqlExpressionTypes.cpp index 4d47e73f20..7148d8018c 100644 --- a/src/engine/sparqlExpressions/SparqlExpressionTypes.cpp +++ b/src/engine/sparqlExpressions/SparqlExpressionTypes.cpp @@ -11,7 +11,7 @@ void PrintTo(const IdOrLiteralOrIri& var, std::ostream* os) { std::visit( [&os](const T& s) { auto& stream = *os; - if constexpr (std::same_as) { + if constexpr (concepts::same_as) { stream << s; } else { stream << s.toStringRepresentation(); diff --git a/src/engine/sparqlExpressions/SparqlExpressionTypes.h b/src/engine/sparqlExpressions/SparqlExpressionTypes.h index 5f3a16a086..c3422efac0 100644 --- a/src/engine/sparqlExpressions/SparqlExpressionTypes.h +++ b/src/engine/sparqlExpressions/SparqlExpressionTypes.h @@ -43,13 +43,14 @@ class VectorWithMemoryLimit // * the last argument must be `AllocatorWithMemoryLimit` (all constructors to // `vector` take the allocator as a last parameter) // * there must be a constructor of `Base` for the given arguments. - template - requires(sizeof...(Args) > 0 && - !std::derived_from>, - Base> && - std::convertible_to, Allocator> && - std::constructible_from) - explicit(sizeof...(Args) == 1) VectorWithMemoryLimit(Args&&... args) + CPP_template(typename... Args)( + requires(sizeof...(Args) > 0) CPP_and CPP_NOT( + concepts::derived_from< + std::remove_cvref_t>, Base>) + CPP_and concepts::convertible_to, Allocator> + CPP_and concepts::constructible_from< + Base, Args&&...>) explicit(sizeof...(Args) == 1) + VectorWithMemoryLimit(Args&&... args) : Base{AD_FWD(args)...} {} // We have to explicitly forward the `initializer_list` constructor because it @@ -111,7 +112,7 @@ using ExpressionResult = ad_utility::TupleToVariant; /// Only the different types contained in the variant `ExpressionResult` (above) /// match this concept. template -concept SingleExpressionResult = +CPP_concept SingleExpressionResult = ad_utility::SimilarToAnyTypeIn; // Copy an expression result. @@ -120,8 +121,9 @@ CPP_template(typename ResultT)( ExpressionResult>) inline ExpressionResult copyExpressionResult(ResultT&& result) { auto copyIfCopyable = - [](const R& x) -> ExpressionResult { - if constexpr (requires { R{AD_FWD(x)}; }) { + [](const R& x) -> CPP_ret(ExpressionResult)( + requires SingleExpressionResult) { + if constexpr (std::is_constructible_v) { return AD_FWD(x); } else { return x.clone(); @@ -221,9 +223,10 @@ struct EvaluationContext { namespace detail { /// Get Id of constant result of type T. -template -requires isConstantResult && std::is_rvalue_reference_v -Id constantExpressionResultToId(T&& result, LocalVocabT& localVocab) { +CPP_template(typename T, typename LocalVocabT)( + requires SingleExpressionResult CPP_and isConstantResult CPP_and + std::is_rvalue_reference_v) Id + constantExpressionResultToId(T&& result, LocalVocabT& localVocab) { if constexpr (ad_utility::isSimilar) { return result; } else if constexpr (ad_utility::isSimilar) { @@ -275,9 +278,7 @@ struct SpecializedFunction { if (!areAllOperandsValid(operands...)) { return std::nullopt; } else { - if constexpr (requires { - Function{}(std::forward(operands)...); - }) { + if constexpr (ranges::invocable) { return Function{}(std::forward(operands)...); } else { AD_FAIL(); @@ -367,8 +368,8 @@ constexpr bool isOperation> = true; // Return the common logical size of the `SingleExpressionResults`. // This is either 1 (in case all the `inputs` are constants) or the // size of the `context`. -template -size_t getResultSize(const EvaluationContext& context, const Inputs&...) { +CPP_template(typename... Inputs)(requires(SingleExpressionResult&&...)) + size_t getResultSize(const EvaluationContext& context, const Inputs&...) { return (... && isConstantResult) ? 1ul : context.size(); } diff --git a/src/engine/sparqlExpressions/SparqlExpressionValueGetters.cpp b/src/engine/sparqlExpressions/SparqlExpressionValueGetters.cpp index 8eda73d065..1767a55481 100644 --- a/src/engine/sparqlExpressions/SparqlExpressionValueGetters.cpp +++ b/src/engine/sparqlExpressions/SparqlExpressionValueGetters.cpp @@ -286,11 +286,11 @@ OptIri IriValueGetter::operator()( } //______________________________________________________________________________ -template -requires std::same_as || - std::same_as, T> -T getValue(ValueId id, const sparqlExpression::EvaluationContext* context, - ValueGetter& valueGetter) { +CPP_template(typename T, typename ValueGetter)( + requires(concepts::same_as || + concepts::same_as, T>)) T + getValue(ValueId id, const sparqlExpression::EvaluationContext* context, + ValueGetter& valueGetter) { using enum Datatype; switch (id.getDatatype()) { case LocalVocabIndex: diff --git a/src/engine/sparqlExpressions/SparqlExpressionValueGetters.h b/src/engine/sparqlExpressions/SparqlExpressionValueGetters.h index bea3a60e3a..96b4167991 100644 --- a/src/engine/sparqlExpressions/SparqlExpressionValueGetters.h +++ b/src/engine/sparqlExpressions/SparqlExpressionValueGetters.h @@ -43,25 +43,29 @@ using OptIri = std::optional; // general args to a numeric value (-> `int64_t or double`). using IntDoubleStr = std::variant; +// Ensures that the T value is convertible to a numeric Id. +template +CPP_concept ValueAsNumericId = + concepts::integral || ad_utility::FloatingPoint || + ad_utility::SimilarToAny; + // Convert a numeric value (either a plain number, or the `NumericValue` variant // from above) into an `ID`. When `NanToUndef` is `true` then floating point NaN // values will become `Id::makeUndefined()`. -template -requires std::integral || std::floating_point || - ad_utility::SimilarToAny -Id makeNumericId(T t) { - if constexpr (std::integral) { +CPP_template(bool NanToUndef = false, + typename T)(requires ValueAsNumericId) Id makeNumericId(T t) { + if constexpr (concepts::integral) { return Id::makeFromInt(t); - } else if constexpr (std::floating_point && NanToUndef) { + } else if constexpr (ad_utility::FloatingPoint && NanToUndef) { return std::isnan(t) ? Id::makeUndefined() : Id::makeFromDouble(t); - } else if constexpr (std::floating_point && !NanToUndef) { + } else if constexpr (ad_utility::FloatingPoint && !NanToUndef) { return Id::makeFromDouble(t); - } else if constexpr (std::same_as) { + } else if constexpr (concepts::same_as) { return Id::makeUndefined(); - } else if constexpr (std::same_as) { + } else if constexpr (concepts::same_as) { return std::visit([](const auto& x) { return makeNumericId(x); }, t); } else { - static_assert(std::same_as); + static_assert(concepts::same_as); return t; } } @@ -266,10 +270,11 @@ struct LiteralFromIdGetter : Mixin { // Convert the input into a `unique_ptr`. Return nullptr if the input is // not convertible to a string. struct RegexValueGetter { - template - requires std::invocable - std::unique_ptr operator()(S&& input, - const EvaluationContext* context) const { + template + auto operator()(S&& input, const EvaluationContext* context) const -> CPP_ret( + std::unique_ptr)( + requires SingleExpressionResult&& + ranges::invocable) { auto str = StringValueGetter{}(AD_FWD(input), context); if (!str.has_value()) { return nullptr; diff --git a/src/engine/sparqlExpressions/StdevExpression.cpp b/src/engine/sparqlExpressions/StdevExpression.cpp index c05e1cfe4d..1524fd9091 100644 --- a/src/engine/sparqlExpressions/StdevExpression.cpp +++ b/src/engine/sparqlExpressions/StdevExpression.cpp @@ -56,8 +56,9 @@ ExpressionResult DeviationExpression::evaluate( }; // Visitor for child expression result - auto impl = [context, - devImpl](SingleExpressionResult auto&& el) -> ExpressionResult { + auto impl = [context, devImpl](auto&& el) + -> CPP_ret(ExpressionResult)( + requires SingleExpressionResult) { // Prepare space for result VectorWithMemoryLimit exprResult{context->_allocator}; exprResult.resize(context->size()); diff --git a/src/engine/sparqlExpressions/StringExpressions.cpp b/src/engine/sparqlExpressions/StringExpressions.cpp index cc39fbca9f..899342c5ed 100644 --- a/src/engine/sparqlExpressions/StringExpressions.cpp +++ b/src/engine/sparqlExpressions/StringExpressions.cpp @@ -57,10 +57,11 @@ class StringExpressionImpl : public SparqlExpression { SparqlExpression::Ptr impl_; public: - explicit StringExpressionImpl( - SparqlExpression::Ptr child, - std::same_as auto... children) - requires(sizeof...(children) + 1 == N) { + CPP_template(typename... C)( + requires(concepts::same_as&&...) + CPP_and(sizeof...(C) + 1 == + N)) explicit StringExpressionImpl(SparqlExpression::Ptr child, + C... children) { AD_CORRECTNESS_CHECK(child != nullptr); if (child->isStrExpression()) { auto childrenOfStr = std::move(*child).moveChildrenOut(); @@ -95,12 +96,13 @@ class StringExpressionImpl : public SparqlExpression { // strings. template struct LiftStringFunction { - template >... Arguments> - auto operator()(Arguments... arguments) const { + CPP_template(typename... Arguments)(requires( + concepts::same_as>&&...)) auto + operator()(Arguments... arguments) const { using ResultOfFunction = decltype(std::invoke(Function{}, std::move(arguments.value())...)); - static_assert(std::same_as || - std::same_as, + static_assert(concepts::same_as || + concepts::same_as, "Template argument of `LiftStringFunction` must return `Id` " "or `std::string`"); using Result = @@ -168,13 +170,13 @@ class SubstrImpl { // Round an integer or floating point to the nearest integer according to the // SPARQL standard. This means that -1.5 is rounded to -1. static constexpr auto round = [](const T& value) -> int64_t { - if constexpr (std::floating_point) { + if constexpr (ad_utility::FloatingPoint) { if (value < 0) { return static_cast(-std::round(-value)); } else { return static_cast(std::round(value)); } - } else if constexpr (std::integral) { + } else if constexpr (concepts::integral) { return static_cast(value); } else { AD_FAIL(); @@ -234,9 +236,9 @@ using SubstrExpression = namespace { -template -requires(isOperation) -class StrStartsExpressionImpl : public NaryExpression { +CPP_template(typename NaryOperation)( + requires isOperation) class StrStartsExpressionImpl + : public NaryExpression { public: using NaryExpression::NaryExpression; std::vector getPrefilterExpressionForMetadata( @@ -380,9 +382,9 @@ class ConcatExpression : public detail::VariadicExpression { // If the result is a string, then all the previously evaluated children // were constants (see above). std::variant result{std::string{""}}; - auto visitSingleExpressionResult = - [&ctx, &result ](T && s) - requires std::is_rvalue_reference_v { + auto visitSingleExpressionResult = CPP_template_lambda( + &ctx, &result)(typename T)(T && s)(requires SingleExpressionResult && + std::is_rvalue_reference_v) { if constexpr (isConstantResult) { std::string strFromConstant = StringValueGetter{}(s, ctx).value_or(""); if (std::holds_alternative(result)) { @@ -545,8 +547,9 @@ using std::make_unique; using std::move; using Expr = SparqlExpression::Ptr; -template -Expr make(std::same_as auto&... children) { +CPP_template(typename T, + typename... C)(requires(concepts::same_as&&...)) Expr + make(C&... children) { return std::make_unique(std::move(children)...); } Expr makeStrExpression(Expr child) { return make(child); } diff --git a/src/util/TypeTraits.h b/src/util/TypeTraits.h index cd7f2f82b4..d864aad96b 100644 --- a/src/util/TypeTraits.h +++ b/src/util/TypeTraits.h @@ -361,4 +361,8 @@ concept RegularInvocableWithExactReturnType = // auto&& x) // guaranteed rvalue reference, can safely be moved. template concept Rvalue = std::is_rvalue_reference_v; + +// Ensures that T is a floating-point type. +template +CPP_concept FloatingPoint = std::is_floating_point_v; } // namespace ad_utility diff --git a/test/RelationalExpressionTest.cpp b/test/RelationalExpressionTest.cpp index 50e369109f..ca4d6c2a77 100644 --- a/test/RelationalExpressionTest.cpp +++ b/test/RelationalExpressionTest.cpp @@ -40,9 +40,10 @@ const auto NaN = std::numeric_limits::quiet_NaN(); // Create and return a `RelationalExpression` with the given Comparison and the // given operands `leftValue` and `rightValue`. -template -auto makeExpression(SingleExpressionResult auto leftValue, - SingleExpressionResult auto rightValue) { +CPP_template(Comparison comp, typename L, typename R)( + requires SingleExpressionResult CPP_and + SingleExpressionResult) auto makeExpression(L leftValue, + R rightValue) { auto leftChild = std::make_unique(std::move(leftValue)); auto rightChild = std::make_unique(std::move(rightValue)); @@ -51,8 +52,10 @@ auto makeExpression(SingleExpressionResult auto leftValue, {std::move(leftChild), std::move(rightChild)}); } -auto makeInExpression(SingleExpressionResult auto leftValue, - SingleExpressionResult auto... rightValues) { +CPP_template(typename L, + typename... R)(requires SingleExpressionResult CPP_and( + SingleExpressionResult&&...)) auto makeInExpression(L leftValue, + R... rightValues) { auto leftChild = std::make_unique(std::move(leftValue)); std::vector rightChildren; (..., (rightChildren.push_back( diff --git a/test/SparqlExpressionTest.cpp b/test/SparqlExpressionTest.cpp index 891b990e92..4820310ca4 100644 --- a/test/SparqlExpressionTest.cpp +++ b/test/SparqlExpressionTest.cpp @@ -99,11 +99,11 @@ concept VectorOrExpressionResult = // Convert a `VectorOrExpressionResult` (see above) to a type that is supported // by the expression module. -template -SingleExpressionResult auto toExpressionResult(T vec) { +CPP_template(typename T)( + requires VectorOrExpressionResult) auto toExpressionResult(T vec) { if constexpr (SingleExpressionResult) { return vec; - } else if constexpr (std::convertible_to) { + } else if constexpr (ranges::convertible_to) { // TODO Make a generic testing utility for the string case... return IdOrLiteralOrIri{lit(vec)}; } else { @@ -157,7 +157,8 @@ requires(!isVectorResult) auto nonVectorResultMatcher(const T& expected) { // storing `Ids` which have to be handled using the `matchId` matcher (see // above) are all handled correctly. auto sparqlExpressionResultMatcher = - [](const Expected& expected) { + [](const Expected& expected) { + CPP_assert(SingleExpressionResult); if constexpr (isVectorResult) { auto matcherVec = ad_utility::transform(expected, [](const auto& singleExpected) { @@ -181,49 +182,50 @@ auto clone = [](const auto& x) { }; // The implementation of `testNaryExpression` directly below. -auto testNaryExpressionImpl = - [](auto&& makeExpression, SingleExpressionResult auto const& expected, - SingleExpressionResult auto const&... operands) { - ad_utility::AllocatorWithLimit alloc{ - ad_utility::testing::makeAllocator()}; - VariableToColumnMap map; - LocalVocab localVocab; - IdTable table{alloc}; - - // Get the size of `operand`: size for a vector, 1 otherwise. - auto getResultSize = [](const T& operand) -> size_t { - if constexpr (isVectorResult) { - return operand.size(); - } - return 1; - }; +auto testNaryExpressionImpl = [](auto&& makeExpression, auto const& expected, + auto const&... operands) { + CPP_assert(SingleExpressionResult && + (SingleExpressionResult && ...)); + ad_utility::AllocatorWithLimit alloc{ + ad_utility::testing::makeAllocator()}; + VariableToColumnMap map; + LocalVocab localVocab; + IdTable table{alloc}; + + // Get the size of `operand`: size for a vector, 1 otherwise. + auto getResultSize = [](const T& operand) -> size_t { + if constexpr (isVectorResult) { + return operand.size(); + } + return 1; + }; - const auto resultSize = [&operands..., &getResultSize]() { - if constexpr (sizeof...(operands) == 0) { - (void)getResultSize; - return 0ul; - } else { - return std::max({getResultSize(operands)...}); - } - }(); + const auto resultSize = [&operands..., &getResultSize]() { + if constexpr (sizeof...(operands) == 0) { + (void)getResultSize; + return 0ul; + } else { + return std::max({getResultSize(operands)...}); + } + }(); - TestContext outerContext; - sparqlExpression::EvaluationContext& context = outerContext.context; - context._endIndex = resultSize; + TestContext outerContext; + sparqlExpression::EvaluationContext& context = outerContext.context; + context._endIndex = resultSize; - std::array children{ - std::make_unique( - ExpressionResult{clone(operands)})...}; + std::array children{ + std::make_unique( + ExpressionResult{clone(operands)})...}; - auto expressionPtr = std::apply(makeExpression, std::move(children)); - auto& expression = *expressionPtr; + auto expressionPtr = std::apply(makeExpression, std::move(children)); + auto& expression = *expressionPtr; - ExpressionResult result = expression.evaluate(&context); + ExpressionResult result = expression.evaluate(&context); - using ExpectedType = std::decay_t; - ASSERT_THAT(result, ::testing::VariantWith( - sparqlExpressionResultMatcher(expected))); - }; + using ExpectedType = std::decay_t; + ASSERT_THAT(result, ::testing::VariantWith( + sparqlExpressionResultMatcher(expected))); +}; // Assert that the given `NaryExpression` with the given `operands` has the // `expected` result. @@ -239,10 +241,11 @@ auto testNaryExpression = [](auto&& makeExpression, // in both orders of the operands `op1` and `op2`. template auto testBinaryExpressionCommutative = - [](const SingleExpressionResult auto& expected, - const SingleExpressionResult auto& op1, - const SingleExpressionResult auto& op2, + [](const auto& expected, const auto& op1, const auto& op2, source_location l = source_location::current()) { + CPP_assert(SingleExpressionResult && + SingleExpressionResult && + SingleExpressionResult); auto t = generateLocationTrace(l); testNaryExpression(makeFunction, expected, op1, op2); testNaryExpression(makeFunction, expected, op2, op1);