Skip to content

Commit

Permalink
Backport of some concepts from util to C++17 (#1741)
Browse files Browse the repository at this point in the history
This affects many files, as the `util/TypeTraits.h` header introduces several general concepts that are used all across the QLever codebase.
  • Loading branch information
gpicciuca authored Feb 13, 2025
1 parent 702339f commit cfc49a9
Show file tree
Hide file tree
Showing 88 changed files with 1,479 additions and 943 deletions.
4 changes: 4 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,8 @@ LineEnding: LF
InsertNewlineAtEOF: true
RequiresClausePosition: SingleLine
IndentRequiresClause: false
AttributeMacros: [CPP_member, CPP_auto_member, CPP_fun, CPP_auto_fun]
# TODO<joka921> Update to Clang-format-17 and then we can have much nicer CPP_... macros.
#Macros:
# - CPP_template_2(x)=template<x> requires
...
36 changes: 19 additions & 17 deletions benchmark/JoinAlgorithmBenchmark.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -770,27 +770,29 @@ class GeneralInterfaceImplementation : public BenchmarkInterface {
};
};
auto generateBiggerEqualLambdaDesc =
[](const ad_utility::isInstantiation<
ad_utility::ConstConfigOptionProxy> auto& option,
const auto& minimumValue, bool canBeEqual) {
return absl::StrCat("'", option.getConfigOption().getIdentifier(),
"' must be bigger than",
canBeEqual ? ", or equal to," : "", " ",
minimumValue, ".");
};
[]<typename OptionType,
typename = std::enable_if_t<ad_utility::isInstantiation<
OptionType, ad_utility::ConstConfigOptionProxy>>>(
const OptionType& option, const auto& minimumValue,
bool canBeEqual) {
return absl::StrCat("'", option.getConfigOption().getIdentifier(),
"' must be bigger than",
canBeEqual ? ", or equal to," : "", " ", minimumValue,
".");
};

// Object with a `operator()` for the `<=` operator.
auto lessEqualLambda = std::less_equal<size_t>{};
auto generateLessEqualLambdaDesc =
[](const ad_utility::isInstantiation<
ad_utility::ConstConfigOptionProxy> auto& lhs,
const ad_utility::isInstantiation<
ad_utility::ConstConfigOptionProxy> auto& rhs) {
return absl::StrCat("'", lhs.getConfigOption().getIdentifier(),
"' must be smaller than, or equal to, "
"'",
rhs.getConfigOption().getIdentifier(), "'.");
};
[]<typename OptionType,
typename = std::enable_if_t<ad_utility::isInstantiation<
OptionType, ad_utility::ConstConfigOptionProxy>>>(
const OptionType& lhs, const OptionType& rhs) {
return absl::StrCat("'", lhs.getConfigOption().getIdentifier(),
"' must be smaller than, or equal to, "
"'",
rhs.getConfigOption().getIdentifier(), "'.");
};

// Adding the validators.

Expand Down
4 changes: 2 additions & 2 deletions benchmark/infrastructure/BenchmarkMeasurementContainer.h
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,8 @@ class ResultTable : public BenchmarkMetadataGetter {
@param row, column Which table entry to read. Starts with `(0,0)`.
*/
template <ad_utility::SameAsAnyTypeIn<EntryType> T>
T getEntry(const size_t row, const size_t column) const {
CPP_template(typename T)(requires ad_utility::SameAsAnyTypeIn<T, EntryType>) T
getEntry(const size_t row, const size_t column) const {
AD_CONTRACT_CHECK(row < numRows() && column < numColumns());
static_assert(!ad_utility::isSimilar<T, std::monostate>);

Expand Down
12 changes: 6 additions & 6 deletions benchmark/infrastructure/BenchmarkToJson.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ provided translation function for the vector entries.
@tparam TranslationFunction Has to be a function, that takes `VectorType`
and returns a `nlohmann:json` object.
*/
template <typename VectorType, ad_utility::InvocableWithExactReturnType<
nlohmann::ordered_json, VectorType>
TranslationFunction>
static nlohmann::json transformIntoJsonArray(
const std::vector<VectorType>& vec,
TranslationFunction translationFunction) {
CPP_template(typename VectorType, typename TranslationFunction)(
requires ad_utility::InvocableWithExactReturnType<
TranslationFunction, nlohmann::ordered_json,
VectorType>) static nlohmann::json
transformIntoJsonArray(const std::vector<VectorType>& vec,
TranslationFunction translationFunction) {
/*
Without explicit instantiation, `nlohmann::nlohmann::ordered_json` is not
guaranteed, to always interpret a `push_back` correctly. For instance,
Expand Down
5 changes: 3 additions & 2 deletions benchmark/util/ResultTableColumnOperations.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ Column number together with the type of value, that can be found inside the
column. Note, that **all** entries in the column must have the same type,
because of `ResultTable::getEntry`.
*/
template <ad_utility::SameAsAnyTypeIn<ResultTable::EntryType> Type>
struct ColumnNumWithType {
CPP_template(typename Type)(
requires ad_utility::SameAsAnyTypeIn<
Type, ResultTable::EntryType>) struct ColumnNumWithType {
using ColumnType = Type;
const size_t columnNum_;
};
Expand Down
25 changes: 21 additions & 4 deletions src/backports/concepts.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#pragma once

#include <concepts/concepts.hpp>

#include "backports/cppTemplate2.h"
#ifndef QLEVER_CPP_17
#include <concepts>
#endif
Expand Down Expand Up @@ -32,24 +34,39 @@
// NOTE: The macros are variadic to allow for commas in the argument, like in
// the second example above.

// Additionally define the macros `CPP_template_2` and `CPP_and_2` that can
// be used to constrain member functions of classes where the outer class
// has already been constrained with `CPP_template`. For a detailed example, see
// the `test/backports/ConceptsTest.cpp` file.

#ifdef QLEVER_CPP_17
#define QL_CONCEPT_OR_NOTHING(...)
#define QL_CONCEPT_OR_TYPENAME(...) typename
#define CPP_template_2 CPP_template_2_SFINAE
#define CPP_and_2 CPP_and_2_sfinae
#define CPP_and_def CPP_and_sfinae_def
#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
#else
#define QL_CONCEPT_OR_NOTHING(...) __VA_ARGS__
#define QL_CONCEPT_OR_TYPENAME(...) __VA_ARGS__
#define CPP_template_2 CPP_template
#define CPP_and_2 CPP_and
#define CPP_and_def CPP_and
#define CPP_and_2_def CPP_and
#define CPP_variadic_template CPP_template
#define CPP_member_def CPP_member
#endif

// The namespace `ql::concepts` includes concepts that are contained in the
// C++20 standard as well as in `range-v3`.
namespace ql {
namespace concepts {
namespace ql::concepts {

#ifdef QLEVER_CPP_17
using namespace ::concepts;
#else
using namespace std;
#endif

} // namespace concepts
} // namespace ql
} // namespace ql::concepts
60 changes: 60 additions & 0 deletions src/backports/cppTemplate2.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright 2024, University of Freiburg
// Chair of Algorithms and Data Structures
// Author: Johannes Kalmbach <[email protected]>

#pragma once
#include <concepts/concepts.hpp>

// The internal reimplementation of a `CPP_variadic_template` macro
// that can be used for variadic template functions.
#define CPP_TEMPLATE_NO_DEFAULT_SFINAE_AUX_0(...) \
, std::enable_if_t<CPP_PP_CAT(CPP_TEMPLATE_SFINAE_AUX_3_, __VA_ARGS__), \
int> = 0 > CPP_PP_IGNORE_CXX2A_COMPAT_END

#define CPP_TEMPLATE_NO_DEFAULT_SFINAE_AUX_3_requires

#define CPP_TEMPLATE_NO_DEFAULT_SFINAE_AUX_WHICH_(FIRST, ...) \
CPP_PP_EVAL(CPP_PP_CHECK, \
CPP_PP_CAT(CPP_TEMPLATE_SFINAE_PROBE_CONCEPT_, FIRST))

#define CPP_TEMPLATE_NO_DEFAULT_SFINAE_AUX_(...) \
CPP_PP_CAT(CPP_TEMPLATE_NO_DEFAULT_SFINAE_AUX_, \
CPP_TEMPLATE_NO_DEFAULT_SFINAE_AUX_WHICH_(__VA_ARGS__, )) \
(__VA_ARGS__)

#define CPP_template_NO_DEFAULT_SFINAE(...) \
CPP_PP_IGNORE_CXX2A_COMPAT_BEGIN \
template <__VA_ARGS__ CPP_TEMPLATE_NO_DEFAULT_SFINAE_AUX_

// The internal reimplementation of a `CPP_template_2` and `CPP_and_2` macro
// that can be used for class members when the outer class has already been
// constrained using `CPP_template`.
#define CPP_TEMPLATE_2_SFINAE_AUX_0(...) \
, bool CPP_true_2 = true, \
std::enable_if_t < \
CPP_PP_CAT(CPP_TEMPLATE_SFINAE_AUX_3_, __VA_ARGS__) && \
CPP_BOOL(CPP_true_2), \
int > = 0 > CPP_PP_IGNORE_CXX2A_COMPAT_END

#define CPP_TEMPLATE_2_SFINAE_AUX_3_requires

#define CPP_TEMPLATE_2_SFINAE_AUX_WHICH_(FIRST, ...) \
CPP_PP_EVAL(CPP_PP_CHECK, \
CPP_PP_CAT(CPP_TEMPLATE_SFINAE_PROBE_CONCEPT_, FIRST))

#define CPP_TEMPLATE_2_SFINAE_AUX_(...) \
CPP_PP_CAT(CPP_TEMPLATE_2_SFINAE_AUX_, \
CPP_TEMPLATE_2_SFINAE_AUX_WHICH_(__VA_ARGS__, )) \
(__VA_ARGS__)

#define CPP_template_2_SFINAE(...) \
CPP_PP_IGNORE_CXX2A_COMPAT_BEGIN \
template <__VA_ARGS__ CPP_TEMPLATE_2_SFINAE_AUX_

/// INTERNAL ONLY
#define CPP_and_def_sfinae &&CPP_BOOL(CPP_true), int >, std::enable_if_t <
#define CPP_and_2_sfinae &&CPP_BOOL(CPP_true_2), int > = 0, std::enable_if_t <
#define CPP_and_2_def_sfinae &&CPP_BOOL(CPP_true_2), int >, std::enable_if_t <

#define CPP_member_def_sfinae \
template <bool (&CPP_true_fn)(::concepts::detail::xNil)>
2 changes: 1 addition & 1 deletion src/engine/ExportQueryExecutionTrees.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -901,7 +901,7 @@ cppcoro::generator<std::string> ExportQueryExecutionTrees::computeResult(

auto inner =
ad_utility::ConstexprSwitch<csv, tsv, octetStream, turtle, sparqlXml,
sparqlJson, qleverJson>(compute, mediaType);
sparqlJson, qleverJson>{}(compute, mediaType);
return convertStreamGeneratorForChunkedTransfer(std::move(inner));
}

Expand Down
17 changes: 9 additions & 8 deletions src/engine/Filter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,10 @@ ProtoResult Filter::computeResult(bool requestLaziness) {
}

// _____________________________________________________________________________
template <ad_utility::SimilarTo<IdTable> Table>
IdTable Filter::filterIdTable(std::vector<ColumnIndex> sortedBy,
Table&& idTable,
const LocalVocab& localVocab) const {
CPP_template_def(typename Table)(requires ad_utility::SimilarTo<Table, IdTable>)
IdTable Filter::filterIdTable(std::vector<ColumnIndex> sortedBy,
Table&& idTable,
const LocalVocab& localVocab) const {
size_t width = idTable.numColumns();
IdTable result{width, getExecutionContext()->getAllocator()};

Expand All @@ -120,10 +120,11 @@ IdTable Filter::filterIdTable(std::vector<ColumnIndex> sortedBy,
}

// _____________________________________________________________________________
template <int WIDTH, ad_utility::SimilarTo<IdTable> Table>
void Filter::computeFilterImpl(IdTable& dynamicResultTable, Table&& inputTable,
const LocalVocab& localVocab,
std::vector<ColumnIndex> sortedBy) const {
CPP_template_def(int WIDTH, typename Table)(
requires ad_utility::SimilarTo<Table, IdTable>) void Filter::
computeFilterImpl(IdTable& dynamicResultTable, Table&& inputTable,
const LocalVocab& localVocab,
std::vector<ColumnIndex> sortedBy) const {
AD_CONTRACT_CHECK(inputTable.numColumns() == WIDTH || WIDTH == 0);
IdTableStatic<WIDTH> resultTable =
std::move(dynamicResultTable).toStatic<static_cast<size_t>(WIDTH)>();
Expand Down
17 changes: 10 additions & 7 deletions src/engine/Filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,16 @@ class Filter : public Operation {
ProtoResult computeResult(bool requestLaziness) override;

// Perform the actual filter operation of the data provided.
template <int WIDTH, ad_utility::SimilarTo<IdTable> Table>
void computeFilterImpl(IdTable& dynamicResultTable, Table&& input,
const LocalVocab& localVocab,
std::vector<ColumnIndex> sortedBy) const;
CPP_template(int WIDTH, typename Table)(
requires ad_utility::SimilarTo<
Table, IdTable>) void computeFilterImpl(IdTable& dynamicResultTable,
Table&& input,
const LocalVocab& localVocab,
std::vector<ColumnIndex>
sortedBy) const;

// Run `computeFilterImpl` on the provided IdTable
template <ad_utility::SimilarTo<IdTable> Table>
IdTable filterIdTable(std::vector<ColumnIndex> sortedBy, Table&& idTable,
const LocalVocab& localVocab) const;
CPP_template(typename Table)(requires ad_utility::SimilarTo<Table, IdTable>)
IdTable filterIdTable(std::vector<ColumnIndex> sortedBy, Table&& idTable,
const LocalVocab& localVocab) const;
};
4 changes: 2 additions & 2 deletions src/engine/Server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -472,8 +472,8 @@ auto Server::cancelAfterDeadline(
// _____________________________________________________________________________
auto Server::setupCancellationHandle(
const ad_utility::websocket::QueryId& queryId, TimeLimit timeLimit)
-> ad_utility::isInstantiation<
CancellationHandleAndTimeoutTimerCancel> auto {
-> QL_CONCEPT_OR_NOTHING(ad_utility::isInstantiation<
CancellationHandleAndTimeoutTimerCancel>) auto {
auto cancellationHandle = queryRegistry_.getCancellationHandle(queryId);
AD_CORRECTNESS_CHECK(cancellationHandle);
cancellationHandle->startWatchDog();
Expand Down
19 changes: 11 additions & 8 deletions src/engine/Server.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,10 @@ class Server {

using SharedCancellationHandle = ad_utility::SharedCancellationHandle;

template <ad_utility::isInstantiation<absl::Cleanup> CancelTimeout>
struct CancellationHandleAndTimeoutTimerCancel {
CPP_template(typename CancelTimeout)(
requires ad_utility::isInstantiation<
CancelTimeout,
absl::Cleanup>) struct CancellationHandleAndTimeoutTimerCancel {
SharedCancellationHandle handle_;
/// Object of type `absl::Cleanup` that when destroyed cancels the timer
/// that would otherwise invoke the cancellation of the `handle_` via the
Expand All @@ -109,10 +111,11 @@ class Server {
// Clang doesn't seem to be able to automatically deduce the type correctly.
// and GCC 11 thinks deduction guides are not allowed within classes.
#ifdef __clang__
template <ad_utility::isInstantiation<absl::Cleanup> CancelTimeout>
CancellationHandleAndTimeoutTimerCancel(SharedCancellationHandle,
CancelTimeout)
-> CancellationHandleAndTimeoutTimerCancel<CancelTimeout>;
CPP_template(typename CancelTimeout)(
requires ad_utility::isInstantiation<CancelTimeout, absl::Cleanup>)
CancellationHandleAndTimeoutTimerCancel(SharedCancellationHandle,
CancelTimeout)
-> CancellationHandleAndTimeoutTimerCancel<CancelTimeout>;
#endif

/// Handle a single HTTP request. Check whether a file request or a query was
Expand Down Expand Up @@ -241,8 +244,8 @@ class Server {
/// member can be invoked to cancel the imminent cancellation via timeout.
auto setupCancellationHandle(const ad_utility::websocket::QueryId& queryId,
TimeLimit timeLimit)
-> ad_utility::isInstantiation<
CancellationHandleAndTimeoutTimerCancel> auto;
-> QL_CONCEPT_OR_NOTHING(ad_utility::isInstantiation<
CancellationHandleAndTimeoutTimerCancel>) auto;

/// Check if the access token is valid. Return true if the access token
/// exists and is valid. Return false if there's no access token passed.
Expand Down
6 changes: 4 additions & 2 deletions src/engine/idTable/IdTableRow.h
Original file line number Diff line number Diff line change
Expand Up @@ -385,8 +385,10 @@ class RowReference
// The `cbegin` and `cend` functions are implicitly inherited from `Base`.

// __________________________________________________________________________
template <ad_utility::SimilarTo<RowReference> R>
friend void swap(R&& a, R&& b) requires(!isConst) {
CPP_template(typename R)(
requires ad_utility::SimilarTo<RowReference, R>) friend void swap(R&& a,
R&& b)
requires(!isConst) {
return Base::swapImpl(AD_FWD(a), AD_FWD(b));
}

Expand Down
9 changes: 7 additions & 2 deletions src/engine/sparqlExpressions/RelationalExpressions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,13 @@ requires isVectorResult<S> auto idGenerator(const S& values, size_t targetSize,

// For the `Variable` and `SetOfIntervals` class, the generator from the
// `sparqlExpressions` module already yields the `ValueIds`.
template <ad_utility::SimilarToAny<Variable, ad_utility::SetOfIntervals> S>
auto idGenerator(S input, size_t targetSize, const EvaluationContext* context) {
CPP_template(typename S)(
requires ad_utility::SimilarToAny<
S, Variable,
ad_utility::SetOfIntervals>) auto idGenerator(S input,
size_t targetSize,
const EvaluationContext*
context) {
return sparqlExpression::detail::makeGenerator(std::move(input), targetSize,
context);
}
Expand Down
17 changes: 10 additions & 7 deletions src/engine/sparqlExpressions/SparqlExpressionTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,10 @@ concept SingleExpressionResult =
ad_utility::SimilarToAnyTypeIn<T, ExpressionResult>;

// Copy an expression result.
inline ExpressionResult copyExpressionResult(
ad_utility::SimilarTo<ExpressionResult> auto&& result) {
CPP_template(typename ResultT)(
requires ad_utility::SimilarTo<ResultT,
ExpressionResult>) inline ExpressionResult
copyExpressionResult(ResultT&& result) {
auto copyIfCopyable =
[]<SingleExpressionResult R>(const R& x) -> ExpressionResult {
if constexpr (requires { R{AD_FWD(x)}; }) {
Expand Down Expand Up @@ -332,11 +334,12 @@ std::optional<ExpressionResult> evaluateOnSpecializedFunctionsIfPossible(
// for multiple `ValueGetters` because there can be multiple `ValueGetters` as
// well as zero or more `SpezializedFunctions`, but there can only be a single
// parameter pack in C++.
template <
size_t NumOperands,
ad_utility::isInstantiation<FunctionAndValueGetters>
FunctionAndValueGettersT,
ad_utility::isInstantiation<SpecializedFunction>... SpecializedFunctions>
template <size_t NumOperands,
QL_CONCEPT_OR_TYPENAME(
ad_utility::isInstantiation<FunctionAndValueGetters>)
FunctionAndValueGettersT,
QL_CONCEPT_OR_TYPENAME(ad_utility::isInstantiation<
SpecializedFunction>)... SpecializedFunctions>
struct Operation {
private:
using OriginalValueGetters = typename FunctionAndValueGettersT::ValueGetters;
Expand Down
Loading

0 comments on commit cfc49a9

Please sign in to comment.