Skip to content

Commit

Permalink
Add C++20 compatible versions of InvocableMap and QueryableMap.
Browse files Browse the repository at this point in the history
Sadly, the syntax the clang extension offers cannot be reproduced with standard C++20. These new map types will allow for C++20 support on non-clang compilers.

#42

PiperOrigin-RevId: 710765161
  • Loading branch information
jwhpryor authored and copybara-github committed Jan 6, 2025
1 parent a5a17d2 commit 49255e6
Show file tree
Hide file tree
Showing 6 changed files with 414 additions and 9 deletions.
53 changes: 53 additions & 0 deletions metaprogramming/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,30 @@ cc_test(
],
)

################################################################################
# Invocable Map 20.
################################################################################
cc_library(
name = "invocable_map_20",
hdrs = ["invocable_map_20.h"],
deps = [
":modified_max",
":string_literal",
],
)

cc_test(
name = "invocable_map_test_20",
srcs = ["invocable_map_20_test.cc"],
tags = ["cpp20"],
deps = [
":invocable_map_20",
":modified_max",
":string_literal",
"@googletest//:gtest_main",
],
)

################################################################################
# Invoke.
################################################################################
Expand Down Expand Up @@ -961,6 +985,35 @@ cc_test(
],
)

################################################################################
# Queryable Map 20.
################################################################################
cc_library(
name = "queryable_map_20",
hdrs = ["queryable_map_20.h"],
deps = [
":interleave",
":lambda_string",
":modified_max",
":string_literal",
":tuple_from_size",
":tuple_manipulation",
":type_of_nth_element",
],
)

cc_test(
name = "queryable_map_20_test",
srcs = ["queryable_map_20_test.cc"],
tags = ["cpp20"],
deps = [
":modified_max",
":queryable_map_20",
":string_literal",
"@googletest//:gtest_main",
],
)

################################################################################
# Reduce.
################################################################################
Expand Down
19 changes: 10 additions & 9 deletions metaprogramming/invocable_map.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
namespace jni::metaprogramming {

template <typename CrtpBase, const auto& tup_container_v,
typename TupContainerT, const auto TupContainerT::*nameable_member>
typename TupContainerT, const auto TupContainerT::* nameable_member>
class InvocableMap;

// This is an interface that can be inherited from to expose an operator(...).
Expand All @@ -42,7 +42,7 @@ class InvocableMap;
// auto InvocableMapCall(const char* key, Args&&... args);
//
// If i is the index where |tup_container_v.*nameable_member|.name_ == key,
// then InvocablemapCall will forward the args from operator() with the
// then InvocableMapCall will forward the args from operator() with the
// same args. Static memory can be used in this function call and it will
// be unique because of the I non-type template parameter.
//
Expand All @@ -54,35 +54,36 @@ class InvocableMap;
// the the const char cannot be propagated without losing its constexpr-ness,
// and so the clang extension can no longer restrict function candidates.
template <typename CrtpBase, const auto& tup_container_v,
const auto std::decay_t<decltype(tup_container_v)>::*nameable_member>
const auto std::decay_t<decltype(tup_container_v)>::* nameable_member>
using InvocableMap_t =
InvocableMap<CrtpBase, tup_container_v,
std::decay_t<decltype(tup_container_v)>, nameable_member>;

template <typename CrtpBase, const auto& tup_container_v,
typename TupContainerT, const auto TupContainerT::*nameable_member,
typename TupContainerT, const auto TupContainerT::* nameable_member,
std::size_t I>
class InvocableMapEntry;

template <typename CrtpBase, const auto& tup_container_v,
typename TupContainerT, const auto TupContainerT::*nameable_member,
typename TupContainerT, const auto TupContainerT::* nameable_member,
typename IndexSequenceType>
class InvocableMapBase {};

template <typename CrtpBase, const auto& tup_container_v,
typename TupContainerT, const auto TupContainerT::*nameable_member,
typename TupContainerT, const auto TupContainerT::* nameable_member,
std::size_t... idxs>
class InvocableMapBase<CrtpBase, tup_container_v, TupContainerT,
nameable_member, std::index_sequence<idxs...>>
: public InvocableMapEntry<CrtpBase, tup_container_v, TupContainerT,
nameable_member, idxs>... {
public:
using InvocableMapEntry<CrtpBase, tup_container_v, TupContainerT,
nameable_member, idxs>::operator()...;
nameable_member, idxs>::
operator()...;
};

template <typename CrtpBase, const auto& tup_container_v,
typename TupContainerT, const auto TupContainerT::*nameable_member,
typename TupContainerT, const auto TupContainerT::* nameable_member,
std::size_t I>
class InvocableMapEntry {
public:
Expand Down Expand Up @@ -116,7 +117,7 @@ class InvocableMapEntry {

//==============================================================================
template <typename CrtpBase, const auto& tup_container_v,
typename TupContainerT, const auto TupContainerT::*nameable_member>
typename TupContainerT, const auto TupContainerT::* nameable_member>
class InvocableMap
: public InvocableMapBase<
CrtpBase, tup_container_v, TupContainerT, nameable_member,
Expand Down
100 changes: 100 additions & 0 deletions metaprogramming/invocable_map_20.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#ifndef JNI_BIND_METAPROGRAMMING_INVOCABLE_MAP_20_H
#define JNI_BIND_METAPROGRAMMING_INVOCABLE_MAP_20_H

#include <cstddef>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <utility>

#include "modified_max.h"
#include "string_literal.h"

namespace jni::metaprogramming {

// This class enables compile time lookup that perfectly forward arguments
// to a method named `Call`. This is a C++ 20 version of InvocableMap.
//
// This is an interface that can be inherited from to expose a method named
// `Call`. It provides compile time string index lookup with no macros.
//
// To use this API, inherit from this class using template types as follows:
//
// |CrtpBase|: The name of the class inheriting from the map. This class
// will inherit an operator(). It must implement this exact signature:
//
// template <std::size_t I, StringLiteral key_literal, typename... Args>
// auto InvocableMap20Call(Args&&... args);
//
// If i is the index where |tup_container_v.*nameable_member|.name_ == key,
// then InvocableMap20Call will forward the args from operator() with the
// same args. Static memory can be used in this function call and it will
// be unique because of the I non-type template parameter.
//
// |tup_container_v| is a static instance of an object whose |nameable_member|
// contains a public field called name_. It might seem strange not to
// directly pass a const auto&, however, this prevents accessing subobjects.
//
// The motivation for using inheritance as opposed to a simple member is that
// the the const char cannot be propagated without losing its constexpr-ness,
// and so the clang extension can no longer restrict function candidates.
template <typename CrtpBase, const auto& tup_container_v,
typename TupContainerT, const auto TupContainerT::* nameable_member>
class InvocableMap20 {
#if __cplusplus >= 202002L
public:
template <StringLiteral key_literal, std::size_t Idx, typename... Args>
constexpr auto Do(Args&&... args) {
return (*static_cast<CrtpBase*>(this))
.template InvocableMap20Call<Idx, key_literal, Args...>(
std::forward<Args>(args)...);
}

template <std::size_t N, std::size_t... Is>
static constexpr std::size_t SelectCandidate(StringLiteral<N> string_literal,
std::index_sequence<Is...>) {
return ModifiedMax(
{((std::get<Is>(tup_container_v.*nameable_member).name_ ==
std::string_view{string_literal.value})
? std::size_t{Is}
: kNegativeOne)...,
kNegativeOne});
}

template <StringLiteral string_literal, typename... Args>
constexpr auto Call(Args&&... args) {
return Do<string_literal,
SelectCandidate(
string_literal,
std::make_index_sequence<std::tuple_size_v<std::decay_t<
decltype(tup_container_v.*nameable_member)>>>())>(
std::forward<Args>(args)...);
}
#endif // __cplusplus >= 202002L
};

template <typename CrtpBase, const auto& tup_container_v,
const auto std::decay_t<decltype(tup_container_v)>::* nameable_member>
using InvocableMap20_t =
InvocableMap20<CrtpBase, tup_container_v,
std::decay_t<decltype(tup_container_v)>, nameable_member>;

} // namespace jni::metaprogramming

#endif // JNI_BIND_METAPROGRAMMING_INVOCABLE_MAP_20_H
80 changes: 80 additions & 0 deletions metaprogramming/invocable_map_20_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "invocable_map_20.h"

#include <cstddef>
#include <string_view>
#include <tuple>
#include <type_traits>

#include "string_literal.h"
#include <gtest/gtest.h>

using jni::metaprogramming::InvocableMap20_t;
using jni::metaprogramming::StringLiteral;

struct Str {
const char* name_;
constexpr Str(const char* name) : name_(name) {}
};

struct NameContainer {
std::tuple<Str, Str, Str> container1_;
std::tuple<Str, Str, Str> container2_;
};

constexpr NameContainer name_container{
{{"Foo"}, {"Bar"}, {"Baz"}},
{{"Fizz"}, {"Buzz"}, {"Bang"}},
};

////////////////////////////////////////////////////////////////////////////////
class SampleClassNowExposingCallOperator1
: public InvocableMap20_t<SampleClassNowExposingCallOperator1,
name_container, &NameContainer::container1_> {
protected:
friend InvocableMap20_t<SampleClassNowExposingCallOperator1, name_container,
&NameContainer::container1_>;

template <size_t I, StringLiteral key_literal, typename... Args>
auto InvocableMap20Call(Args&&... ts) {
if (std::string_view(key_literal.value) == "Foo") {
EXPECT_TRUE(I == 0);
EXPECT_TRUE((std::is_same_v<std::tuple<Args...>, std::tuple<int>>));
} else if (std::string_view(key_literal.value) == "Bar") {
EXPECT_TRUE(I == 1);
EXPECT_TRUE(
(std::is_same_v<std::tuple<Args...>, std::tuple<float, float>>));
} else if (std::string_view(key_literal.value) == "Baz") {
EXPECT_TRUE(I == 2);
EXPECT_TRUE((
std::is_same_v<std::tuple<Args...>, std::tuple<int, float, double>>));
} else {
FAIL();
}
}
};

TEST(InvocableMapTest1, HasCorrectTypesAndForwardsCalls) {
SampleClassNowExposingCallOperator1 val;
val.Call<"Foo">(1);
val.Call<"Bar">(2.f, 3.f);
val.Call<"Baz">(4, 5.f, double{6});

// By design, doesn't compile.
// val("BazNar", 7, 8, 9);
}
Loading

0 comments on commit 49255e6

Please sign in to comment.