From 1570033d07eb625dd3c2624c866eeb241f8639ef Mon Sep 17 00:00:00 2001 From: gpicciuca Date: Fri, 14 Feb 2025 08:46:53 +0100 Subject: [PATCH] Backport concepts in util/parser module for C++17 (#1799) Replace all C++20 concept usages in the `util/parser` module using v3-range macros and/or the custom made ones from `backport/concepts`. --- src/backports/concepts.h | 17 +++++++ src/backports/cppTemplate2.h | 44 ++++++++++++++++ src/parser/GeoPoint.h | 5 +- src/parser/Iri.h | 5 +- src/parser/Literal.h | 6 ++- src/parser/LiteralOrIri.h | 6 +-- src/parser/ParallelParseBuffer.h | 18 +++++-- src/parser/RdfParser.h | 5 +- src/parser/TokenizerCtre.h | 1 + src/parser/TripleComponent.h | 40 ++++++++++----- .../sparqlParser/SparqlQleverVisitor.cpp | 51 ++++++++++--------- src/parser/sparqlParser/SparqlQleverVisitor.h | 20 ++++---- test/backports/ConceptsTest.cpp | 15 ++++++ 13 files changed, 172 insertions(+), 61 deletions(-) diff --git a/src/backports/concepts.h b/src/backports/concepts.h index 17a2f3e5c4..942466f23d 100644 --- a/src/backports/concepts.h +++ b/src/backports/concepts.h @@ -21,6 +21,13 @@ // `QL_CONCEPT_OR_TYPENAME(arg)`: expands to `arg` in C++20 mode, and to // `typename` in C++17 mode. Example usage: // +// `CPP_lambda(capture)(arg)(requires ...)`: Expands lambda to use +// `requires` in C++20 mode and `std::enable_if_t` in C++17 mode. +// +// `CPP_template_lambda(capture)(typenames...)(arg)(requires ...)`: Expands +// lambda with (C++20) explicit gemplate parameters to use +// `requires` in C++20 mode and `std::enable_if_t` in C++17 mode. +// // Example usages: // // `QL_CONCEPT_OR_NOTHING(std::view) auto x = someFunction();` @@ -31,6 +38,12 @@ // // `template ) T> void f(){...}` // +// `auto myLambda = CPP_lambda(someCapture)(someArg)(requires +// ranges::same_as) {...}` +// +// `auto myLambda2 = CPP_lambda(someCapture)(typename F)(F arg)(requires +// ranges::same_as) {...}` +// // NOTE: The macros are variadic to allow for commas in the argument, like in // the second example above. @@ -48,6 +61,8 @@ #define CPP_and_2_def CPP_and_2_def_sfinae #define CPP_variadic_template CPP_template_NO_DEFAULT_SFINAE #define CPP_member_def CPP_member_def_sfinae +#define CPP_lambda CPP_lambda_sfinae +#define CPP_template_lambda CPP_template_lambda_sfinae #else #define QL_CONCEPT_OR_NOTHING(...) __VA_ARGS__ #define QL_CONCEPT_OR_TYPENAME(...) __VA_ARGS__ @@ -57,6 +72,8 @@ #define CPP_and_2_def CPP_and #define CPP_variadic_template CPP_template #define CPP_member_def CPP_member +#define CPP_lambda CPP_LAMBDA_20 +#define CPP_template_lambda CPP_TEMPLATE_LAMBDA_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 a5c89b2c16..171dc5dca4 100644 --- a/src/backports/cppTemplate2.h +++ b/src/backports/cppTemplate2.h @@ -58,3 +58,47 @@ #define CPP_member_def_sfinae \ template + +#define CPP_LAMBDA_20(...) [__VA_ARGS__] CPP_LAMBDA_ARGS + +#define CPP_TEMPLATE_LAMBDA_20(...) [__VA_ARGS__] CPP_TEMPLATE_LAMBDA_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)) + +#define CPP_LAMBDA_SFINAE_AUX_(...) \ + CPP_PP_CAT(CPP_LAMBDA_SFINAE_AUX_, \ + CPP_LAMBDA_SFINAE_AUX_WHICH_(__VA_ARGS__, )) \ + (__VA_ARGS__) + +#define CPP_LAMBDA_SFINAE_AUX_0(...) , \ +std::enable_if_t< \ +CPP_PP_CAT(CPP_LAMBDA_SFINAE_AUX_3_, __VA_ARGS__) \ +>* = nullptr) + +#define CPP_lambda_sfinae(...) \ + CPP_PP_IGNORE_CXX2A_COMPAT_BEGIN \ + [__VA_ARGS__] CPP_LAMBDA_SFINAE_ARGS + +#define CPP_TEMPLATE_LAMBDA_ARGS_sfinae(...) \ + <__VA_ARGS__> CPP_LAMBDA_SFINAE_ARGS + +#define CPP_template_lambda_sfinae(...) \ + [__VA_ARGS__] CPP_TEMPLATE_LAMBDA_ARGS_sfinae + +#define CPP_LAMBDA_SFINAE_AUX_3_requires + +#define CPP_LAMBDA_ARGS(...) (__VA_ARGS__) CPP_LAMBDA_AUX_ + +#define CPP_LAMBDA_AUX_(...) \ + CPP_PP_CAT(CPP_LAMBDA_AUX_, CPP_LAMBDA_AUX_WHICH_(__VA_ARGS__, ))(__VA_ARGS__) + +#define CPP_LAMBDA_AUX_WHICH_(FIRST, ...) CPP_PP_EVAL(CPP_PP_CHECK, FIRST) + +#define CPP_LAMBDA_AUX_0(...) __VA_ARGS__ + +#define CPP_TEMPLATE_LAMBDA_ARGS(...) <__VA_ARGS__> CPP_LAMBDA_ARGS diff --git a/src/parser/GeoPoint.h b/src/parser/GeoPoint.h index 9a4a1a6eb1..d3cc9d1e82 100644 --- a/src/parser/GeoPoint.h +++ b/src/parser/GeoPoint.h @@ -40,8 +40,9 @@ class GeoPoint { public: using T = uint64_t; - template - friend H AbslHashValue(H h, const std::same_as auto& g) { + CPP_template(typename H, + typename G)(requires std::same_as) friend H + AbslHashValue(H h, const G& g) { return H::combine(std::move(h), g.lat_, g.lng_); } diff --git a/src/parser/Iri.h b/src/parser/Iri.h index f302ae26cc..dc20e479fa 100644 --- a/src/parser/Iri.h +++ b/src/parser/Iri.h @@ -7,6 +7,7 @@ #include +#include "backports/concepts.h" #include "parser/NormalizedString.h" namespace ad_utility::triple_component { @@ -30,8 +31,8 @@ class Iri { public: // A default constructed IRI is empty. Iri() = default; - template - friend H AbslHashValue(H h, const std::same_as auto& iri) { + CPP_template(typename H, typename I)(requires std::same_as) friend H + AbslHashValue(H h, const I& iri) { return H::combine(std::move(h), iri.iri_); } bool operator==(const Iri&) const = default; diff --git a/src/parser/Literal.h b/src/parser/Literal.h index 5367c261ae..a02c4cb100 100644 --- a/src/parser/Literal.h +++ b/src/parser/Literal.h @@ -7,6 +7,7 @@ #include #include +#include "backports/concepts.h" #include "parser/Iri.h" #include "parser/NormalizedString.h" @@ -41,8 +42,9 @@ class Literal { } public: - template - friend H AbslHashValue(H h, const std::same_as auto& literal) { + CPP_template(typename H, + typename L)(requires std::same_as) friend H + AbslHashValue(H h, const L& literal) { return H::combine(std::move(h), literal.content_); } bool operator==(const Literal&) const = default; diff --git a/src/parser/LiteralOrIri.h b/src/parser/LiteralOrIri.h index 04cc4c32af..18be5d79a5 100644 --- a/src/parser/LiteralOrIri.h +++ b/src/parser/LiteralOrIri.h @@ -50,9 +50,9 @@ class alignas(16) LiteralOrIri { return LiteralOrIri{Iri::fromStringRepresentation(std::move(internal))}; } } - template - friend H AbslHashValue(H h, - const std::same_as auto& literalOrIri) { + CPP_template(typename H, + typename L)(requires std::same_as) friend H + AbslHashValue(H h, const L& literalOrIri) { return H::combine(std::move(h), literalOrIri.data_); } bool operator==(const LiteralOrIri&) const = default; diff --git a/src/parser/ParallelParseBuffer.h b/src/parser/ParallelParseBuffer.h index 6ad203a499..53e1b33452 100644 --- a/src/parser/ParallelParseBuffer.h +++ b/src/parser/ParallelParseBuffer.h @@ -15,6 +15,16 @@ using std::array; using std::string; using std::vector; +namespace ad_utility::detail { + +template +CPP_requires(ParserGetBatchRequires, requires(Parser& p)(p.getBatch())); + +template +CPP_concept ParserGetBatch = CPP_requires_ref(ParserGetBatchRequires, Parser); + +} // namespace ad_utility::detail + /** * A wrapper to make the different Parsers interfaces compatible with the * parallel pipeline @@ -57,11 +67,9 @@ class ParserBatcher { } } - // The second requires evaluates to `true` only if the `Parser` type has a - // getBatch() member function. The first requires enables this function only - // if the second "requires" evaluates to true - std::optional> getBatch() - requires requires(Parser& p) { p.getBatch(); } { + CPP_member auto getBatch() + -> CPP_ret(std::optional>)( + requires ad_utility::detail::ParserGetBatch) { if (m_numTriplesAlreadyParsed >= m_maxNumTriples) { return std::nullopt; } diff --git a/src/parser/RdfParser.h b/src/parser/RdfParser.h index 063cfc35e3..1fb36871c9 100644 --- a/src/parser/RdfParser.h +++ b/src/parser/RdfParser.h @@ -459,8 +459,9 @@ class NQuadParser : public TurtleParser { * Parses turtle from std::string. Used to perform unit tests for * the different parser rules */ -template Parser> -class RdfStringParser : public Parser { +CPP_template(typename Parser)( + requires std::derived_from) class RdfStringParser + : public Parser { public: using Parser::getLine; using Parser::prefixMap_; diff --git a/src/parser/TokenizerCtre.h b/src/parser/TokenizerCtre.h index 1801b448f1..28c2e48731 100644 --- a/src/parser/TokenizerCtre.h +++ b/src/parser/TokenizerCtre.h @@ -336,6 +336,7 @@ class TokenizerCtre : public SkipWhitespaceAndCommentsMixin { * on success and on failure */ struct Matcher { + // TODO: Template-value feature not available in C++17 template static std::pair process( std::string_view data) noexcept { diff --git a/src/parser/TripleComponent.h b/src/parser/TripleComponent.h index 84abe54f7e..2b6ced96d9 100644 --- a/src/parser/TripleComponent.h +++ b/src/parser/TripleComponent.h @@ -22,6 +22,17 @@ #include "util/Exception.h" #include "util/Forward.h" +namespace ad_utility::detail { + +template +CPP_requires(MoveAssignableWithRequires, requires(T t, U&& u)(t = u)); + +template +CPP_concept MoveAssignableWith = + CPP_requires_ref(MoveAssignableWithRequires, T, U); + +} // namespace ad_utility::detail + /// A wrapper around a `std::variant` that can hold the different types that the /// subject, predicate, or object of a triple can have in the Turtle Parser. /// Those currently are `double` (xsd:double and xsd:decimal), `int64_t` @@ -56,10 +67,11 @@ class TripleComponent { TripleComponent() = default; /// Construct from anything that is able to construct the underlying /// `Variant`. - template - requires(!std::same_as, TripleComponent> && - std::is_constructible_v) - TripleComponent(FirstArg&& firstArg, Args&&... args) + CPP_template(typename FirstArg, typename... Args)( + requires CPP_NOT( + std::same_as, TripleComponent>) && + std::is_constructible_v) + TripleComponent(FirstArg&& firstArg, Args&&... args) : _variant(AD_FWD(firstArg), AD_FWD(args)...) { if (isString()) { // Storing variables and literals as strings is deprecated. The following @@ -83,9 +95,9 @@ class TripleComponent { /// Assignment for types that can be directly assigned to the underlying /// variant. - template - requires requires(Variant v, T&& t) { _variant = t; } - TripleComponent& operator=(T&& value) { + CPP_template(typename T)(requires std::is_assignable_v) + TripleComponent& + operator=(T&& value) { _variant = AD_FWD(value); checkThatStringIsValid(); return *this; @@ -103,10 +115,11 @@ class TripleComponent { TripleComponent& operator=(TripleComponent&&) = default; /// Make a `TripleComponent` directly comparable to the underlying types. - template - requires requires(T&& t) { _variant == t; } - bool operator==(const T& other) const { - return _variant == other; + CPP_template(typename T)( + requires ad_utility::SameAsAnyTypeIn) bool + operator==(const T& other) const { + auto ptr = std::get_if(&_variant); + return ptr && *ptr == other; } /// Equality comparison between two `TripleComponent`s. @@ -117,8 +130,9 @@ class TripleComponent { /// overload would also be eligible for the contained types that are /// implicitly convertible to `TripleComponent` which would lead to strange /// bugs. - template - friend H AbslHashValue(H h, const std::same_as auto& tc) { + CPP_template(typename H, + typename TC)(requires std::same_as) friend H + AbslHashValue(H h, const TC& tc) { return H::combine(std::move(h), tc._variant); } diff --git a/src/parser/sparqlParser/SparqlQleverVisitor.cpp b/src/parser/sparqlParser/SparqlQleverVisitor.cpp index f3d051145d..85ced1ed73 100644 --- a/src/parser/sparqlParser/SparqlQleverVisitor.cpp +++ b/src/parser/sparqlParser/SparqlQleverVisitor.cpp @@ -7,7 +7,6 @@ #include "parser/sparqlParser/SparqlQleverVisitor.h" -#include #include #include @@ -2308,20 +2307,22 @@ ExpressionPtr Visitor::visit([[maybe_unused]] Parser::BuiltInCallContext* ctx) { using namespace sparqlExpression; // Create the expression using the matching factory function from // `NaryExpression.h`. - auto createUnary = [&argList](Function function) - requires std::is_invocable_r_v { + auto createUnary = CPP_template_lambda(&argList)(typename F)(F function)( + requires std::is_invocable_r_v) { AD_CORRECTNESS_CHECK(argList.size() == 1, argList.size()); return function(std::move(argList[0])); }; - auto createBinary = [&argList](Function function) - requires std::is_invocable_r_v { + + auto createBinary = CPP_template_lambda(&argList)(typename F)(F function)( + requires std::is_invocable_r_v) { AD_CORRECTNESS_CHECK(argList.size() == 2); return function(std::move(argList[0]), std::move(argList[1])); }; - auto createTernary = [&argList](Function function) - requires std::is_invocable_r_v { + + auto createTernary = CPP_template_lambda(&argList)(typename F)(F function)( + requires std::is_invocable_r_v) { AD_CORRECTNESS_CHECK(argList.size() == 3); return function(std::move(argList[0]), std::move(argList[1]), std::move(argList[2])); @@ -2475,9 +2476,11 @@ SparqlExpression::Ptr Visitor::visit(Parser::StrReplaceExpressionContext* ctx) { reportError( ctx, "REPLACE expressions with four arguments (including regex flags) are " - "currently not supported by QLever. You can however incorporate flags " + "currently not supported by QLever. You can however incorporate " + "flags " "directly into a regex by prepending `(?)` to your regex. For " - "example `(?i)[ei]` will match the regex `[ei]` in a case-insensitive " + "example `(?i)[ei]` will match the regex `[ei]` in a " + "case-insensitive " "way."); } return sparqlExpression::makeReplaceExpression(std::move(children.at(0)), @@ -2535,8 +2538,8 @@ ExpressionPtr Visitor::visit(Parser::AggregateContext* ctx) { std::string separator; if (ctx->string()) { // TODO: The string rule also allow triple quoted strings with different - // escaping rules. These are currently not handled. They should be parsed - // into a typesafe format with a unique representation. + // escaping rules. These are currently not handled. They should be + // parsed into a typesafe format with a unique representation. separator = visit(ctx->string()).get(); // If there was a separator, we have to strip the quotation marks AD_CONTRACT_CHECK(separator.size() >= 2); @@ -2652,19 +2655,20 @@ GraphTerm Visitor::visit(Parser::BlankNodeContext* ctx) { } // ____________________________________________________________________________________ -template -void Visitor::visitVector(const std::vector& childContexts) - requires voidWhenVisited { +CPP_template_def(typename Ctx)( + requires Visitor::voidWhenVisited) void Visitor:: + visitVector(const vector& childContexts) { for (const auto& child : childContexts) { visit(child); } } // ____________________________________________________________________________________ -template -[[nodiscard]] auto Visitor::visitVector(const std::vector& childContexts) - -> std::vector - requires(!voidWhenVisited) { +CPP_template_def(typename Ctx)( + requires CPP_NOT(Visitor::voidWhenVisited)) + [[nodiscard]] auto Visitor::visitVector( + const std::vector& childContexts) + -> std::vector { std::vector children; for (const auto& child : childContexts) { children.emplace_back(visit(child)); @@ -2681,7 +2685,8 @@ Out Visitor::visitAlternative(Contexts*... ctxs) { (..., visitIf(ctxs)); } else { std::optional out; - // Visit the one `context` which is not null and write the result to `out`. + // Visit the one `context` which is not null and write the result to + // `out`. (..., visitIf, Out>(&out, ctxs)); return std::move(out.value()); } @@ -2706,8 +2711,8 @@ void Visitor::visitIf(Target* target, Ctx* ctx) { } // _____________________________________________________________________________ -template -void Visitor::visitIf(Ctx* ctx) requires voidWhenVisited { +CPP_template_def(typename Ctx)(requires Visitor::voidWhenVisited< + Visitor, Ctx>) void Visitor::visitIf(Ctx* ctx) { if (ctx) { visit(ctx); } diff --git a/src/parser/sparqlParser/SparqlQleverVisitor.h b/src/parser/sparqlParser/SparqlQleverVisitor.h index b3d629145a..c1d891aa8e 100644 --- a/src/parser/sparqlParser/SparqlQleverVisitor.h +++ b/src/parser/sparqlParser/SparqlQleverVisitor.h @@ -534,16 +534,18 @@ class SparqlQleverVisitor { std::array{std::move(children)...}); } - template - void visitVector(const std::vector& childContexts) - requires voidWhenVisited; + CPP_template(typename Ctx)( + requires SparqlQleverVisitor::voidWhenVisited< + SparqlQleverVisitor, Ctx>) void visitVector(const std::vector& + childContexts); // Call `visit` for each of the `childContexts` and return the results of // those calls as a `vector`. - template - auto visitVector(const std::vector& childContexts) - -> std::vector - requires(!voidWhenVisited); + CPP_template(typename Ctx)(requires CPP_NOT( + SparqlQleverVisitor::voidWhenVisited< + SparqlQleverVisitor, Ctx>)) auto visitVector(const std::vector& + childContexts) + -> std::vector; // Check that exactly one of the `ctxs` is not `null`, visit that context, // cast the result to `Out` and return it. Requires that for all of the @@ -566,8 +568,8 @@ class SparqlQleverVisitor { template void visitIf(Target* target, Ctx* ctx); - template - void visitIf(Ctx* ctx) requires voidWhenVisited; + CPP_template(typename Ctx)(requires SparqlQleverVisitor::voidWhenVisited< + SparqlQleverVisitor, Ctx>) void visitIf(Ctx* ctx); public: [[noreturn]] static void reportError(const antlr4::ParserRuleContext* ctx, diff --git a/test/backports/ConceptsTest.cpp b/test/backports/ConceptsTest.cpp index 5452e3a046..8814a941b9 100644 --- a/test/backports/ConceptsTest.cpp +++ b/test/backports/ConceptsTest.cpp @@ -39,3 +39,18 @@ CPP_template(typename T)(requires(Something)) struct C { // the `CPP_and...` macros won't work here. CPP_variadic_template(typename... Ts)( requires(Something&& Something)) void f(Ts...) {} + +TEST(ConceptBackports, lambdas) { + int i = 3; + auto f = CPP_lambda(i)(auto t)(requires std::is_same_v) { + return i + t; + }; + auto g = CPP_template_lambda(&i)(typename T)(T t)( + requires std::is_same_v) { + return (i++) + t; + }; + + EXPECT_EQ(g(4), 7); + EXPECT_EQ(f(5), 8); + EXPECT_EQ(i, 4); +}