Skip to content

Commit

Permalink
Add macros QL_CONCEPT_OR_NOTHING and QL_CONCEPT_OR_TEMPLATE (#1686)
Browse files Browse the repository at this point in the history
The two macros can be used for concepts that improve the semantics and safety of the code, but are not necessary for compilation. They can be dropped or replaced by something simpler when compiling with C++17.

The macro `QL_CONCEPT_OR_NOTHING` can be used for concepts that can be omitted when compiling with C++17. For example, `QL_CONCEPT_OR_NOTHING(std::view) auto x = someFunction()`.

The macro `QL_CONCEPT_OR_TEMPLATE` can be used for concepts that can be replaced by `typename` when compiling with C++17. For example, `template <QL_CONCEPT_OR_TYPENAME(ql::same_as<int>) T> void f(){...}`.
  • Loading branch information
joka921 authored Dec 18, 2024
1 parent 332d8e5 commit 97c195a
Show file tree
Hide file tree
Showing 16 changed files with 101 additions and 62 deletions.
3 changes: 2 additions & 1 deletion benchmark/JoinAlgorithmBenchmark.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1328,7 +1328,8 @@ class GeneralInterfaceImplementation : public BenchmarkInterface {
*/
bool addNewRowToBenchmarkTable(
ResultTable* table,
const ad_utility::SameAsAny<float, size_t> auto changingParameterValue,
const QL_CONCEPT_OR_NOTHING(
ad_utility::SameAsAny<float, size_t>) auto changingParameterValue,
ad_utility::InvocableWithExactReturnType<bool, float, size_t, size_t,
size_t, size_t, float,
float> auto stopFunction,
Expand Down
5 changes: 3 additions & 2 deletions benchmark/infrastructure/Benchmark.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#include "../benchmark/infrastructure/BenchmarkMeasurementContainer.h"
#include "../benchmark/infrastructure/BenchmarkMetadata.h"
#include "backports/concepts.h"
#include "util/ConfigManager/ConfigManager.h"
#include "util/CopyableUniquePtr.h"
#include "util/Exception.h"
Expand Down Expand Up @@ -67,8 +68,8 @@ class BenchmarkResults {
@param constructorArgs Arguments to pass to the constructor of the object,
that the new `CopyableUniquePtr` will own.
*/
template <
ad_utility::SameAsAny<ResultTable, ResultEntry, ResultGroup> EntryType>
template <QL_CONCEPT_OR_TYPENAME(
ad_utility::SameAsAny<ResultTable, ResultEntry, ResultGroup>) EntryType>
static EntryType& addEntryToContainerVector(
PointerVector<EntryType>& targetVector, auto&&... constructorArgs) {
targetVector.push_back(ad_utility::make_copyable_unique<EntryType>(
Expand Down
2 changes: 1 addition & 1 deletion benchmark/infrastructure/BenchmarkMeasurementContainer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ std::ostream& operator<<(std::ostream& os, const ResultGroup& resultGroup) {
}

// ____________________________________________________________________________
template <ad_utility::SameAsAny<ResultEntry, ResultTable> T>
template <typename T>
void ResultGroup::deleteEntryImpl(T& entry) {
// The vector, that holds our entries.
auto& vec = [this]() -> auto& {
Expand Down
2 changes: 1 addition & 1 deletion benchmark/infrastructure/BenchmarkMeasurementContainer.h
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ class ResultGroup : public BenchmarkMetadataGetter {

private:
// The implementation for the general deletion of entries.
template <ad_utility::SameAsAny<ResultEntry, ResultTable> T>
template <typename T>
void deleteEntryImpl(T& entry);
};

Expand Down
13 changes: 2 additions & 11 deletions src/backports/algorithm.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#include <range/v3/all.hpp>
#include <utility>

#include "backports/concepts.h"

// 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
Expand All @@ -19,7 +21,6 @@
// 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

Expand All @@ -46,14 +47,4 @@ 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
58 changes: 48 additions & 10 deletions src/backports/concepts.h
Original file line number Diff line number Diff line change
@@ -1,17 +1,55 @@
// Copyright 2024, University of Freiburg,
// Chair of Algorithms and Data Structures.
// Author: Johannes Kalmbach <[email protected]>
// Copyright 2024, University of Freiburg
// Chair of Algorithms and Data Structures
// Author: Johannes Kalmbach <[email protected]>

#pragma once

#include <concepts/concepts.hpp>
#ifndef QLEVER_CPP_17
#include <concepts>
#endif

// 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();`
//
// `QL_CONCEPT_OR_NOTHING(arg)`: 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.
//
// `QL_CONCEPT_OR_TYPENAME(arg)`: expands to `arg` in C++20 mode, and to
// `typename` in C++17 mode. Example usage:
//
// Example usages:
//
// `QL_CONCEPT_OR_NOTHING(std::view) auto x = someFunction();`
//
// `QL_CONCEPT_OR_NOTHING(SameAsAny<int, float>)`
//
// `void f(QL_CONCEPT_OR_NOTHING(std::view) auto x) {...}`
//
// `template <QL_CONCEPT_OR_TYPENAME(ql::same_as<int>) T> void f(){...}`
//
// NOTE: The macros are variadic to allow for commas in the argument, like in
// the second example above.

#ifdef QLEVER_CPP_17
#define QL_OPT_CONCEPT(arg)
#define QL_CONCEPT_OR_NOTHING(...)
#define QL_CONCEPT_OR_TYPENAME(...) typename
#else
#define QL_OPT_CONCEPT(arg) arg
#define QL_CONCEPT_OR_NOTHING(...) __VA_ARGS__
#define QL_CONCEPT_OR_TYPENAME(...) __VA_ARGS__
#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 {

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

} // namespace concepts
} // namespace ql
9 changes: 4 additions & 5 deletions src/engine/sparqlExpressions/LiteralExpression.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,10 @@ class LiteralExpression : public SparqlExpression {
// Evaluating just returns the constant/literal value.
ExpressionResult evaluate(EvaluationContext* context) const override {
// Common code for the `Literal` and `Iri` case.
auto getIdOrString =
[this,
&context](const ad_utility::SameAsAny<TripleComponent::Literal,
TripleComponent::Iri> auto& s)
-> ExpressionResult {
auto getIdOrString = [this, &context]<typename U>(const U& s)
-> CPP_ret(ExpressionResult)(
requires ad_utility::SameAsAny<U, TripleComponent::Literal,
TripleComponent::Iri>) {
if (auto ptr = cachedResult_.load(std::memory_order_relaxed)) {
return *ptr;
}
Expand Down
8 changes: 5 additions & 3 deletions src/util/ConfigManager/ConfigManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <variant>

#include "backports/algorithm.h"
#include "backports/concepts.h"
#include "util/Algorithm.h"
#include "util/ComparisonWithNan.h"
#include "util/ConfigManager/ConfigExceptions.h"
Expand Down Expand Up @@ -254,7 +255,8 @@ requires std::is_object_v<HashMapType> auto ConfigManager::allHashMapEntries(
}

// ____________________________________________________________________________
template <SameAsAny<ConfigOption&, const ConfigOption&> ReturnReference>
template <QL_CONCEPT_OR_TYPENAME(SameAsAny<ConfigOption&, const ConfigOption&>)
ReturnReference>
std::vector<std::pair<std::string, ReturnReference>>
ConfigManager::configurationOptionsImpl(
SimilarTo<ad_utility::HashMap<std::string, HashMapEntry>> auto&
Expand Down Expand Up @@ -855,7 +857,7 @@ bool ConfigManager::containsOption(const ConfigOption& opt) const {
}

// ____________________________________________________________________________
template <ConfigOptionOrManager T>
template <QL_CONCEPT_OR_TYPENAME(ConfigOptionOrManager) T>
void ConfigManager::ConfigurationDocValidatorAssignment::addEntryUnderKey(
const T& key, const ConfigOptionValidatorManager& manager) {
getHashMapBasedOnType<T>()[&key].push_back(&manager);
Expand All @@ -869,7 +871,7 @@ template void ConfigManager::ConfigurationDocValidatorAssignment::
const ConfigOptionValidatorManager&);

// ____________________________________________________________________________
template <ConfigOptionOrManager T>
template <QL_CONCEPT_OR_TYPENAME(ConfigOptionOrManager) T>
auto ConfigManager::ConfigurationDocValidatorAssignment::getEntriesUnderKey(
const T& key) const -> ValueGetterReturnType {
// The concerned hash map.
Expand Down
14 changes: 8 additions & 6 deletions src/util/ConfigManager/ConfigManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <variant>
#include <vector>

#include "backports/concepts.h"
#include "util/ConfigManager/ConfigExceptions.h"
#include "util/ConfigManager/ConfigOption.h"
#include "util/ConfigManager/ConfigOptionProxy.h"
Expand All @@ -34,7 +35,7 @@ namespace ConfigManagerImpl {
// Shorthand concepts, to reduce code duplication.
class ConfigManager;
template <typename T>
concept ConfigOptionOrManager = SameAsAny<T, ConfigOption, ConfigManager>;
CPP_concept ConfigOptionOrManager = SameAsAny<T, ConfigOption, ConfigManager>;

/*
Manages a bunch of `ConfigOption`s.
Expand Down Expand Up @@ -579,7 +580,8 @@ class ConfigManager {
@tparam ReturnReference Should be either `ConfigOption&`, or `const
ConfigOption&`.
*/
template <SameAsAny<ConfigOption&, const ConfigOption&> ReturnReference>
template <QL_CONCEPT_OR_TYPENAME(
SameAsAny<ConfigOption&, const ConfigOption&>) ReturnReference>
static std::vector<std::pair<std::string, ReturnReference>>
configurationOptionsImpl(
SimilarTo<ad_utility::HashMap<std::string, HashMapEntry>> auto&
Expand Down Expand Up @@ -716,7 +718,7 @@ class ConfigManager {
@brief Add a validator to the list of validators, that are assigned to a
`ConfigOption`/`ConfigManager`.
*/
template <ConfigOptionOrManager T>
template <QL_CONCEPT_OR_TYPENAME(ConfigOptionOrManager) T>
void addEntryUnderKey(const T& key,
const ConfigOptionValidatorManager& manager);

Expand All @@ -726,20 +728,20 @@ class ConfigManager {
@returns If there is no entry for `Key`, return an empty `std::vector`.
*/
template <ConfigOptionOrManager T>
template <QL_CONCEPT_OR_TYPENAME(ConfigOptionOrManager) T>
ValueGetterReturnType getEntriesUnderKey(const T& key) const;

private:
// Return either `configOption_` or `configManager_`, based on type.
template <ConfigOptionOrManager T>
template <QL_CONCEPT_OR_TYPENAME(ConfigOptionOrManager) T>
constexpr const MemoryAdressHashMap<T>& getHashMapBasedOnType() const {
if constexpr (std::same_as<T, ConfigOption>) {
return configOption_;
} else if constexpr (std::same_as<T, ConfigManager>) {
return configManager_;
}
}
template <ConfigOptionOrManager T>
template <QL_CONCEPT_OR_TYPENAME(ConfigOptionOrManager) T>
constexpr MemoryAdressHashMap<T>& getHashMapBasedOnType() {
if constexpr (std::same_as<T, ConfigOption>) {
return configOption_;
Expand Down
8 changes: 4 additions & 4 deletions src/util/ConfigManager/ConfigOptionProxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ access to the referenced config option.
@tparam ConfigOptionType The kind of config option, this proxy will reference
to. Must be `ConfigOption`, or `const ConfigOption`.
*/
template <
SupportedConfigOptionType T,
ad_utility::SameAsAny<ConfigOption, const ConfigOption> ConfigOptionType>
class ConfigOptionProxyImplementation {
CPP_template(typename T, typename ConfigOptionType)(
requires SupportedConfigOptionType<T> CPP_and ad_utility::SameAsAny<
ConfigOptionType, ConfigOption,
const ConfigOption>) class ConfigOptionProxyImplementation {
ConfigOptionType* option_;

public:
Expand Down
17 changes: 11 additions & 6 deletions src/util/FsstCompressor.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <string>
#include <vector>

#include "util/Concepts.h"
#include "util/Exception.h"
#include "util/Log.h"
#include "util/TypeTraits.h"
Expand All @@ -22,12 +23,16 @@ namespace detail {
// `const unsigned char*` which is used below because FSST always works on
// unsigned character types. Note that this is one of the few cases where a
// `reinterpret_cast` is safe.
constexpr auto castToUnsignedPtr =
[]<ad_utility::SameAsAny<char*, const char*> T>(T ptr) {
using Res = std::conditional_t<std::same_as<T, const char*>,
const unsigned char*, unsigned char*>;
return reinterpret_cast<Res>(ptr);
};
struct CastToUnsignedPtr {
CPP_template(typename T)(
requires ad_utility::SameAsAny<T, char*, const char*>) auto
operator()(T ptr) const {
using Res = std::conditional_t<std::same_as<T, const char*>,
const unsigned char*, unsigned char*>;
return reinterpret_cast<Res>(ptr);
};
};
constexpr CastToUnsignedPtr castToUnsignedPtr{};
} // namespace detail

// A simple C++ wrapper around the C-API of the `FSST` library. It consists of
Expand Down
6 changes: 2 additions & 4 deletions src/util/Iterators.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
// Chair of Algorithms and Data Structures.
// Author: Johannes Kalmbach <[email protected]>

#ifndef QLEVER_ITERATORS_H
#define QLEVER_ITERATORS_H
#pragma once

#include <cstdint>
#include <iterator>
#include <type_traits>

#include "backports/algorithm.h"
#include "util/Enums.h"
#include "util/TypeTraits.h"

Expand Down Expand Up @@ -399,5 +399,3 @@ class InputRangeTypeErased {
using iterator = typename InputRangeFromGet<ValueType>::Iterator;
};
} // namespace ad_utility

#endif // QLEVER_ITERATORS_H
5 changes: 3 additions & 2 deletions src/util/JoinAlgorithms/JoinAlgorithms.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <ranges>

#include "backports/algorithm.h"
#include "backports/concepts.h"
#include "engine/idTable/IdTable.h"
#include "global/Id.h"
#include "util/Generator.h"
Expand Down Expand Up @@ -762,8 +763,8 @@ struct BlockZipperJoinImpl {
#if defined(Side) || defined(Blocks)
#error Side or Blocks are already defined
#endif
#define Side SameAsAny<LeftSide, RightSide> auto
#define Blocks SameAsAny<LeftBlocks, RightBlocks> auto
#define Side QL_CONCEPT_OR_NOTHING(SameAsAny<LeftSide, RightSide>) auto
#define Blocks QL_CONCEPT_OR_NOTHING(SameAsAny<LeftBlocks, RightBlocks>) auto

// Type alias for the result of the projection. Elements from the left and
// right input must be projected to the same type.
Expand Down
4 changes: 2 additions & 2 deletions src/util/TypeTraits.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
#include <variant>
#include <vector>

#include "backports/algorithm.h"
#include "backports/concepts.h"
#include "util/Forward.h"

namespace ad_utility {
Expand Down Expand Up @@ -136,7 +136,7 @@ concept SimilarToAny = (... || isSimilar<T, Ts>);

/// True iff `T` is the same as any of the `Ts...`.
template <typename T, typename... Ts>
concept SameAsAny = (... || ql::concepts::same_as<T, Ts>);
CPP_concept SameAsAny = (... || ql::concepts::same_as<T, Ts>);

/*
The implementation for `SimilarToAnyTypeIn` and `SameAsAnyTypeIn` (see below
Expand Down
3 changes: 2 additions & 1 deletion src/util/Views.h
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,8 @@ CPP_template(typename Range, typename ElementType)(
generator<std::span<ElementType>> reChunkAtSeparator(
Range generator, ElementType separator) {
std::vector<ElementType> buffer;
for (QL_OPT_CONCEPT(ql::ranges::input_range) auto const& chunk : generator) {
for (QL_CONCEPT_OR_NOTHING(ql::ranges::input_range) auto const& chunk :
generator) {
for (ElementType c : chunk) {
if (c == separator) {
co_yield std::span{buffer.data(), buffer.size()};
Expand Down
6 changes: 3 additions & 3 deletions test/ConfigOptionProxyTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ namespace ad_utility {
@tparam OptionType Exists to define, if the test should be done with
`ConfigOption`, or `const ConfigOption`.
*/
template <template <typename> typename ProxyType,
SameAsAny<ConfigOption, const ConfigOption> OptionType>
void basicConstructorTest() {
CPP_template(template <typename> typename ProxyType, typename OptionType)(
requires SameAsAny<OptionType, ConfigOption,
const ConfigOption>) void basicConstructorTest() {
// Test construction for a given type.
auto doTest = []<typename T>() {
// Does the normal constructor work?
Expand Down

0 comments on commit 97c195a

Please sign in to comment.