Skip to content

Commit

Permalink
For C++17, use range-v3 instead of std::ranges (#1667)
Browse files Browse the repository at this point in the history
This is a first step towards making QLever compile with C++17.

If the compile-time flag `QLEVER_CPP_17` is set, use Eric Niebler's `range-v3` library as a drop-in replacement for `std::ranges`. In the code, we simply write `ql::ranges` instead of `std::ranges` in most places. Some places need special treatment. For example, where `std::ranges` was used as a C++20 concept, we now use the macros `CPP_template` and `CPP_and` (also from the `range-v3` library), which does the right thing for both C++20 and C++17.
  • Loading branch information
joka921 authored Dec 12, 2024
1 parent 70964d6 commit 4237e0d
Show file tree
Hide file tree
Showing 163 changed files with 1,258 additions and 1,026 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/native-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ jobs:
- compiler: clang
compiler-version: 13
include:
- compiler: gcc
compiler-version: 11
additional-cmake-options: "-DUSE_CPP_17_BACKPORTS=ON"
build-type: Release
- compiler: clang
compiler-version: 16
asan-flags: "-fsanitize=address -fno-omit-frame-pointer"
Expand Down
22 changes: 21 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,17 @@ FetchContent_Declare(
SOURCE_SUBDIR runtime/Cpp
)

#################################
# Range v3 (for C++-17 backwards compatibility)
################################
FetchContent_Declare(
range-v3
GIT_REPOSITORY https://github.com/joka921/range-v3
GIT_TAG 1dc0b09abab1bdc7d085a78754abd5c6e37a5d0c # 0.12.0
)



################################
# Threading
################################
Expand Down Expand Up @@ -184,6 +195,14 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")
# Enable the specification of additional compiler flags manually from the commandline
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${ADDITIONAL_COMPILER_FLAGS}")

# Enable the manual usage of the C++ 17 backports (currently `range-v3` instead
# of `std::ranges` and the `std::enable_if_t` based expansion of the concept
# macros from `range-v3`.
set(USE_CPP_17_BACKPORTS OFF CACHE BOOL "Use the C++17 backports (range-v3 and enable_if_t instead of std::ranges and concepts)")
if (${USE_CPP_17_BACKPORTS})
add_definitions("-DQLEVER_CPP_17 -DCPP_CXX_CONCEPTS=0")
endif()

# Enable the specification of additional linker flags manually from the commandline
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${ADDITIONAL_LINKER_FLAGS}")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${ADDITIONAL_LINKER_FLAGS}")
Expand Down Expand Up @@ -321,7 +340,7 @@ FetchContent_Declare(
################################
# Apply FetchContent
################################
FetchContent_MakeAvailable(googletest ctre abseil re2 stxxl fsst s2 nlohmann-json antlr)
FetchContent_MakeAvailable(googletest ctre abseil re2 stxxl fsst s2 nlohmann-json antlr range-v3)
# Disable some warnings in RE2, STXXL, and GTEST
target_compile_options(s2 PRIVATE -Wno-sign-compare -Wno-unused-parameter -Wno-class-memaccess -Wno-comment -Wno-redundant-move -Wno-unknown-warning-option -Wno-maybe-uninitialized -Wno-class-memaccess -Wno-unused-but-set-variable -Wno-unused-function)
target_compile_options(re2 PRIVATE -Wno-unused-parameter)
Expand All @@ -333,6 +352,7 @@ include_directories(${ctre_SOURCE_DIR}/single-header)
target_compile_options(fsst PRIVATE -Wno-extra -Wno-all -Wno-error)
target_compile_options(fsst12 PRIVATE -Wno-extra -Wno-all -Wno-error)
include_directories(${fsst_SOURCE_DIR})
include_directories(${range-v3_SOURCE_DIR}/include)
target_compile_options(antlr4_static PRIVATE -Wno-all -Wno-extra -Wno-error -Wno-deprecated-declarations)
# Only required because a lot of classes that do not explicitly link against antlr4_static use the headers.
include_directories(SYSTEM "${antlr_SOURCE_DIR}/runtime/Cpp/runtime/src")
Expand Down
59 changes: 59 additions & 0 deletions src/backports/algorithm.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright 2024, University of Freiburg,
// Chair of Algorithms and Data Structures.
// Author: Johannes Kalmbach <[email protected]>

#pragma once

#include <algorithm>
#include <functional>
#include <range/v3/all.hpp>
#include <utility>

// The following defines namespaces `ql::ranges` and `ql::views` that are almost
// drop-in replacements for `std::ranges` and `std::views`. In C++20 mode (when
// the `QLEVER_CPP_17` macro is not used), these namespaces are simply aliases
// for `std::ranges` and `std::views`. In C++17 mode they contain the ranges and
// views from Erice Niebler's `range-v3` library. NOTE: `ql::ranges::unique`
// currently doesn't work, because the interface to this function is different
// in both implementations. NOTE: There might be other caveats which we are
// currently not aware of, because they only affect functions that we currently
// don't use. For those, the following header can be expanded in the future.
#ifndef QLEVER_CPP_17
#include <concepts>
#include <ranges>
#endif

namespace ql {

namespace ranges {
#ifdef QLEVER_CPP_17
using namespace ::ranges;

// The `view` concept (which is rather important when implementing custom views)
// is in a different namespace in range-v3, so we make it manually accessible.
template <typename T>
CPP_concept view = ::ranges::cpp20::view<T>;
#else
using namespace std::ranges;
#endif
} // namespace ranges

namespace views {
#ifdef QLEVER_CPP_17
using namespace ::ranges::views;
#else
using namespace std::views;
#endif
} // namespace views

// The namespace `ql::concepts` includes concepts that are contained in the
// C++20 standard as well as in `range-v3`.
namespace concepts {
#ifdef QLEVER_CPP_17
using namespace ::concepts;
#else
using namespace std;
#endif
} // namespace concepts

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

#pragma once

// Define the following macros:
// `QL_OPT_CONCEPT(arg)` which expands to `arg` in C++20 mode, and to nothing in
// C++17 mode. It can be used to easily opt out of concepts that are only used
// for documentation and increased safety and not for overload resolution.
// Example usage:
// `(QL_OPT_CONCEPT(std::view) auto x = someFunction();`
#ifdef QLEVER_CPP_17
#define QL_OPT_CONCEPT(arg)
#else
#define QL_OPT_CONCEPT(arg) arg
#endif
4 changes: 2 additions & 2 deletions src/engine/AddCombinedRowToTable.h
Original file line number Diff line number Diff line change
Expand Up @@ -349,8 +349,8 @@ class AddCombinedRowToIdTable {
// Make sure to reset `mergedVocab_` so it is in a valid state again.
mergedVocab_ = LocalVocab{};
// Only merge non-null vocabs.
auto range = currentVocabs_ | std::views::filter(toBool) |
std::views::transform(dereference);
auto range = currentVocabs_ | ql::views::filter(toBool) |
ql::views::transform(dereference);
mergedVocab_.mergeWith(std::ranges::ref_view{range});
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/engine/Bind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ IdTable Bind::cloneSubView(const IdTable& idTable,
const std::pair<size_t, size_t>& subrange) {
IdTable result(idTable.numColumns(), idTable.getAllocator());
result.resize(subrange.second - subrange.first);
std::ranges::copy(idTable.begin() + subrange.first,
idTable.begin() + subrange.second, result.begin());
ql::ranges::copy(idTable.begin() + subrange.first,
idTable.begin() + subrange.second, result.begin());
return result;
}

Expand Down
4 changes: 2 additions & 2 deletions src/engine/CallFixedSize.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ template <int maxValue, size_t NumValues, std::integral Int>
auto callLambdaForIntArray(std::array<Int, NumValues> array, auto&& lambda,
auto&&... args) {
AD_CONTRACT_CHECK(
std::ranges::all_of(array, [](auto el) { return el <= maxValue; }));
ql::ranges::all_of(array, [](auto el) { return el <= maxValue; }));
using ArrayType = std::array<Int, NumValues>;

// Call the `lambda` when the correct compile-time `Int`s are given as a
Expand Down Expand Up @@ -131,7 +131,7 @@ decltype(auto) callFixedSize(std::array<Int, NumIntegers> ints, auto&& functor,
static_assert(NumIntegers > 0);
// TODO<joka921, C++23> Use `std::bind_back`
auto p = [](int i) { return detail::mapToZeroIfTooLarge(i, MaxValue); };
std::ranges::transform(ints, ints.begin(), p);
ql::ranges::transform(ints, ints.begin(), p);

// The only step that remains is to lift our single runtime `value` which
// is in the range `[0, (MaxValue +1)^ NumIntegers]` to a compile-time
Expand Down
41 changes: 20 additions & 21 deletions src/engine/CartesianProductJoin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ CartesianProductJoin::CartesianProductJoin(
children_{std::move(children)},
chunkSize_{chunkSize} {
AD_CONTRACT_CHECK(!children_.empty());
AD_CONTRACT_CHECK(std::ranges::all_of(
AD_CONTRACT_CHECK(ql::ranges::all_of(
children_, [](auto& child) { return child != nullptr; }));

// Check that the variables of the passed in operations are in fact
Expand All @@ -25,22 +25,22 @@ CartesianProductJoin::CartesianProductJoin(
// false as soon as a duplicate is encountered.
ad_utility::HashSet<Variable> vars;
auto checkVarsForOp = [&vars](const Operation& op) {
return std::ranges::all_of(
op.getExternallyVisibleVariableColumns() | std::views::keys,
return ql::ranges::all_of(
op.getExternallyVisibleVariableColumns() | ql::views::keys,
[&vars](const Variable& variable) {
return vars.insert(variable).second;
});
};
return std::ranges::all_of(childView(), checkVarsForOp);
return ql::ranges::all_of(childView(), checkVarsForOp);
}();
AD_CONTRACT_CHECK(variablesAreDisjoint);
}

// ____________________________________________________________________________
std::vector<QueryExecutionTree*> CartesianProductJoin::getChildren() {
std::vector<QueryExecutionTree*> result;
std::ranges::copy(
children_ | std::views::transform([](auto& ptr) { return ptr.get(); }),
ql::ranges::copy(
children_ | ql::views::transform([](auto& ptr) { return ptr.get(); }),
std::back_inserter(result));
return result;
}
Expand All @@ -49,28 +49,28 @@ std::vector<QueryExecutionTree*> CartesianProductJoin::getChildren() {
string CartesianProductJoin::getCacheKeyImpl() const {
return "CARTESIAN PRODUCT JOIN " +
ad_utility::lazyStrJoin(
std::views::transform(
ql::views::transform(
childView(), [](auto& child) { return child.getCacheKey(); }),
" ");
}

// ____________________________________________________________________________
size_t CartesianProductJoin::getResultWidth() const {
auto view = childView() | std::views::transform(&Operation::getResultWidth);
auto view = childView() | ql::views::transform(&Operation::getResultWidth);
return std::reduce(view.begin(), view.end(), 0UL, std::plus{});
}

// ____________________________________________________________________________
size_t CartesianProductJoin::getCostEstimate() {
auto childSizes =
childView() | std::views::transform(&Operation::getCostEstimate);
childView() | ql::views::transform(&Operation::getCostEstimate);
return getSizeEstimate() +
std::reduce(childSizes.begin(), childSizes.end(), 0UL, std::plus{});
}

// ____________________________________________________________________________
uint64_t CartesianProductJoin::getSizeEstimateBeforeLimit() {
auto view = childView() | std::views::transform(&Operation::getSizeEstimate);
auto view = childView() | ql::views::transform(&Operation::getSizeEstimate);
return std::reduce(view.begin(), view.end(), 1UL, std::multiplies{});
}

Expand All @@ -86,7 +86,7 @@ float CartesianProductJoin::getMultiplicity([[maybe_unused]] size_t col) {
bool CartesianProductJoin::knownEmptyResult() {
// If children were empty, returning false would be the wrong behavior.
AD_CORRECTNESS_CHECK(!children_.empty());
return std::ranges::any_of(childView(), &Operation::knownEmptyResult);
return ql::ranges::any_of(childView(), &Operation::knownEmptyResult);
}

// ____________________________________________________________________________
Expand Down Expand Up @@ -138,16 +138,15 @@ ProtoResult CartesianProductJoin::computeResult(bool requestLaziness) {
LocalVocab staticMergedVocab{};
staticMergedVocab.mergeWith(
subResults |
std::views::transform([](const auto& result) -> const LocalVocab& {
ql::views::transform([](const auto& result) -> const LocalVocab& {
return result->localVocab();
}));

if (!requestLaziness) {
AD_CORRECTNESS_CHECK(!lazyResult);
return {
writeAllColumns(subResults | std::views::transform(&Result::idTable),
getLimit()._offset, getLimit().limitOrDefault()),
resultSortedOn(), std::move(staticMergedVocab)};
return {writeAllColumns(subResults | ql::views::transform(&Result::idTable),
getLimit()._offset, getLimit().limitOrDefault()),
resultSortedOn(), std::move(staticMergedVocab)};
}

if (lazyResult) {
Expand All @@ -159,7 +158,7 @@ ProtoResult CartesianProductJoin::computeResult(bool requestLaziness) {
// Owning view wrapper to please gcc 11.
return {produceTablesLazily(std::move(staticMergedVocab),
ad_utility::OwningView{std::move(subResults)} |
std::views::transform(&Result::idTable),
ql::views::transform(&Result::idTable),
getLimit()._offset, getLimit().limitOrDefault()),
resultSortedOn()};
}
Expand Down Expand Up @@ -192,11 +191,11 @@ IdTable CartesianProductJoin::writeAllColumns(
// single result is left. This can probably be done by using the
// `ProtoResult`.

auto sizesView = std::views::transform(idTables, &IdTable::size);
auto sizesView = ql::views::transform(idTables, &IdTable::size);
auto totalResultSize =
std::reduce(sizesView.begin(), sizesView.end(), 1UL, std::multiplies{});

if (!std::ranges::empty(idTables) && sizesView.back() != 0) {
if (!ql::ranges::empty(idTables) && sizesView.back() != 0) {
totalResultSize += (totalResultSize / sizesView.back()) * lastTableOffset;
} else {
AD_CORRECTNESS_CHECK(lastTableOffset == 0);
Expand Down Expand Up @@ -254,7 +253,7 @@ CartesianProductJoin::calculateSubResults(bool requestLaziness) {

std::shared_ptr<const Result> lazyResult = nullptr;
auto children = childView();
AD_CORRECTNESS_CHECK(!std::ranges::empty(children));
AD_CORRECTNESS_CHECK(!ql::ranges::empty(children));
// Get all child results (possibly with limit, see above).
for (Operation& child : children) {
if (limitIfPresent.has_value() && child.supportsLimit()) {
Expand Down Expand Up @@ -346,7 +345,7 @@ Result::Generator CartesianProductJoin::createLazyConsumer(
size_t producedTableSize = 0;
for (auto& idTableAndVocab : produceTablesLazily(
std::move(localVocab),
std::views::transform(
ql::views::transform(
idTables,
[](const auto& wrapper) -> const IdTable& { return wrapper; }),
offset, limit, lastTableOffset)) {
Expand Down
9 changes: 4 additions & 5 deletions src/engine/CartesianProductJoin.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,14 @@ class CartesianProductJoin : public Operation {
// TODO<joka921> We can move this whole children management into a base class
// and clean up the implementation of several other children.
auto childView() {
return std::views::transform(children_, [](auto& child) -> Operation& {
return ql::views::transform(children_, [](auto& child) -> Operation& {
return *child->getRootOperation();
});
}
auto childView() const {
return std::views::transform(children_,
[](auto& child) -> const Operation& {
return *child->getRootOperation();
});
return ql::views::transform(children_, [](auto& child) -> const Operation& {
return *child->getRootOperation();
});
}

public:
Expand Down
Loading

0 comments on commit 4237e0d

Please sign in to comment.