Skip to content

Commit

Permalink
Add a constexpr function for iterating over nested types (#1147)
Browse files Browse the repository at this point in the history
This function allows to automatically call a given templated function for all types that are contained e.g. in a std::variant or std::tuple. This is mostly used for writing more concise unit tests for templated functions.
  • Loading branch information
schlegan authored Nov 17, 2023
1 parent fae57cc commit 2ad3482
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 16 deletions.
8 changes: 2 additions & 6 deletions src/util/ConfigManager/ConfigOption.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,8 @@ void ConfigOption::setValueWithJson(const nlohmann::json& json) {
data_)) {
// Does the json represent one of the types in our `AvailableTypes`? If yes,
// we can create a better exception message.
ad_utility::ConstexprForLoop(
std::make_index_sequence<std::variant_size_v<AvailableTypes>>{},
[&isValueTypeSubType, &json,
this ]<size_t TypeIndex,
typename AlternativeType =
std::variant_alternative_t<TypeIndex, AvailableTypes>>() {
forEachTypeInTemplateType<AvailableTypes>(
[&isValueTypeSubType, &json, this]<typename AlternativeType>() {
if (isValueTypeSubType.template operator()<AlternativeType>(
json, isValueTypeSubType)) {
throw ConfigOptionSetWrongJsonTypeException(
Expand Down
38 changes: 38 additions & 0 deletions src/util/ConstexprUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@

#pragma once

#include <concepts>
#include <ranges>

#include "util/Exception.h"
#include "util/Forward.h"
#include "util/TypeTraits.h"

// Various helper functions for compile-time programming.

Expand Down Expand Up @@ -199,4 +201,40 @@ auto cartesianPowerAsIntegerArray() {
return toIntegerSequence<cartesianPowerAsArray<Upper, Num>()>();
}

/*
@brief Call the given lambda function with each of the given types `Ts` as
explicit template parameter, keeping the same order.
*/
template <typename... Ts>
constexpr void forEachTypeInParameterPack(const auto& lambda) {
(lambda.template operator()<Ts>(), ...);
}

/*
Implementation for `forEachTypeInTemplateType`.
In order to go through the types inside a templated type, we need to use
template type specialization.
*/
namespace detail {
template <class T>
struct forEachTypeInTemplateTypeImpl;

template <template <typename...> typename Template, typename... Ts>
struct forEachTypeInTemplateTypeImpl<Template<Ts...>> {
void operator()(const auto& lambda) const {
forEachTypeInParameterPack<Ts...>(lambda);
}
};
} // namespace detail

/*
@brief Call the given lambda function with each type in the given instantiated
template type as explicit template parameter, keeping the same order.
*/
template <typename TemplateType>
constexpr void forEachTypeInTemplateType(const auto& lambda) {
detail::forEachTypeInTemplateTypeImpl<TemplateType>{}(lambda);
}

} // namespace ad_utility
6 changes: 2 additions & 4 deletions test/BenchmarkMeasurementContainerTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,8 @@ arguments. Should be passed per deduction.
*/
template <typename Function>
static void doForTypeInResultTableEntryType(Function function) {
ad_utility::ConstexprForLoop(
std::make_index_sequence<std::variant_size_v<ResultTable::EntryType>>{},
[&function]<size_t index, typename IndexType = std::variant_alternative_t<
index, ResultTable::EntryType>>() {
ad_utility::forEachTypeInTemplateType<ResultTable::EntryType>(
[&function]<typename IndexType>() {
// `std::monostate` is not important for these kinds of tests.
if constexpr (!ad_utility::isSimilar<IndexType, std::monostate>) {
function.template operator()<IndexType>();
Expand Down
79 changes: 79 additions & 0 deletions test/ConstexprUtilsTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

#include "gtest/gtest.h"
#include "util/ConstexprUtils.h"
#include "util/Exception.h"
#include "util/GTestHelpers.h"
#include "util/TypeTraits.h"

using namespace ad_utility;

Expand Down Expand Up @@ -170,3 +173,79 @@ TEST(ConstexprUtils, ConstexprSwitch) {
static_assert(std::invocable<decltype(ConstexprSwitch<0, 1>), F1, int>);
static_assert(!std::invocable<decltype(ConstexprSwitch<0, 1, 2>), F1, int>);
}

/*
@brief Create a lambda, that adds the string representation of a (supported)
type to a given vector.
@returns A lambda, that takes an explicit template type parameter and adds the
string representation at the end of `*typeToStringVector`.
*/
auto typeToStringFactory(std::vector<std::string>* typeToStringVector) {
return [typeToStringVector]<typename T>() {
if constexpr (ad_utility::isSimilar<T, int>) {
typeToStringVector->emplace_back("int");
} else if constexpr (ad_utility::isSimilar<T, bool>) {
typeToStringVector->emplace_back("bool");
} else if constexpr (ad_utility::isSimilar<T, std::string>) {
typeToStringVector->emplace_back("std::string");
} else {
AD_FAIL();
}
};
}

/*
@brief Test a normal call for a `constExprForEachType` function.
@param callToForEachWrapper A lambda wrapper, that takes an explicit template
parameter pack and a lambda function argument, which it passes to a
`constExprForEachType` function in the correct form.
*/
void testConstExprForEachNormalCall(
const auto& callToForEachWrapper,
ad_utility::source_location l = ad_utility::source_location::current()) {
// For generating better messages, when failing a test.
auto trace{generateLocationTrace(l, "testConstExprForEachNormalCall")};

std::vector<std::string> typeToStringVector{};
auto typeToString = typeToStringFactory(&typeToStringVector);

// Normal call.
callToForEachWrapper.template
operator()<int, bool, std::string, bool, bool, int, int, int>(typeToString);

ASSERT_STREQ(typeToStringVector.at(0).c_str(), "int");
ASSERT_STREQ(typeToStringVector.at(1).c_str(), "bool");
ASSERT_STREQ(typeToStringVector.at(2).c_str(), "std::string");
ASSERT_STREQ(typeToStringVector.at(3).c_str(), "bool");
ASSERT_STREQ(typeToStringVector.at(4).c_str(), "bool");
ASSERT_STREQ(typeToStringVector.at(5).c_str(), "int");
ASSERT_STREQ(typeToStringVector.at(6).c_str(), "int");
ASSERT_STREQ(typeToStringVector.at(7).c_str(), "int");
}

TEST(ConstexprUtils, ForEachTypeInParameterPack) {
// Normal call.
testConstExprForEachNormalCall([]<typename... Ts>(const auto& func) {
forEachTypeInParameterPack<Ts...>(func);
});

// No types given should end in nothing happening.
std::vector<std::string> typeToStringVector{};
auto typeToString = typeToStringFactory(&typeToStringVector);
forEachTypeInParameterPack<>(typeToString);
ASSERT_TRUE(typeToStringVector.empty());
}

TEST(ConstexprUtils, forEachTypeInTemplateType) {
// Normal call with `std::variant`.
testConstExprForEachNormalCall([]<typename... Ts>(const auto& func) {
forEachTypeInTemplateType<std::variant<Ts...>>(func);
});

// Normal call with `std::tuple`.
testConstExprForEachNormalCall([]<typename... Ts>(const auto& func) {
forEachTypeInTemplateType<std::tuple<Ts...>>(func);
});
}
9 changes: 3 additions & 6 deletions test/util/ConfigOptionHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#pragma once
#include "util/ConfigManager/ConfigOption.h"
#include "util/ConstexprUtils.h"

/*
@brief Call the function with each of the alternatives in
Expand All @@ -15,10 +16,6 @@ arguments. Should be passed per deduction.
*/
template <typename Function>
static void doForTypeInConfigOptionValueType(Function function) {
ad_utility::ConstexprForLoop(
std::make_index_sequence<std::variant_size_v<ad_utility::ConfigOption::AvailableTypes>>{},
[&function]<size_t index, typename IndexType = std::variant_alternative_t<
index, ad_utility::ConfigOption::AvailableTypes>>() {
function.template operator()<IndexType>();
});
ad_utility::forEachTypeInTemplateType<
ad_utility::ConfigOption::AvailableTypes>(function);
}

0 comments on commit 2ad3482

Please sign in to comment.