From 3c309065d40cc8f077829c87df1864ee8b9c5087 Mon Sep 17 00:00:00 2001 From: "Alan M. Carroll" Date: Mon, 20 Aug 2018 11:14:26 -0500 Subject: [PATCH] Meta: Add conditional compilation case meta structures. --- include/tscore/ts_meta.h | 98 ++++++++++++++++++++++++ src/tscore/Makefile.am | 1 + src/tscore/unit_tests/test_ts_meta.cc | 106 ++++++++++++++++++++++++++ 3 files changed, 205 insertions(+) create mode 100644 include/tscore/ts_meta.h create mode 100644 src/tscore/unit_tests/test_ts_meta.cc diff --git a/include/tscore/ts_meta.h b/include/tscore/ts_meta.h new file mode 100644 index 00000000000..a79c8f3955c --- /dev/null +++ b/include/tscore/ts_meta.h @@ -0,0 +1,98 @@ +/** @file + + Meta programming support utilities. + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. + See the NOTICE file distributed with this work for additional information regarding copyright + ownership. The ASF licenses this file to you 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. +*/ + +#pragma once + +namespace ts +{ +namespace meta +{ + /** This creates an ordered series of meta template cases that can be used to select one of a set + * of functions in a priority ordering. A set of templated overloads take an (extra) argument of + * the case structures, each a different one. Calling the function invokes the highest case that + * is valid. Because of SFINAE the templates can have errors, as long as at least one doesn't. + * The root technique is to use @c decltype to check an expression for the overload to be valid. + * Because the compiler will evaluate everything it can while parsing the template this + * expression must be delayed until the template is instantiated. This is done by making the + * return type @c auto and making the @c decltype dependent on the template parameter. In + * addition, the comma operator can be used to force a specific return type while also checking + * the expression for validity. E.g. + * + * @code + * template auto func(T && t, CaseArg_0 const&) -> decltype(t.item, int()) { } + * @endcode + * + * The comma operator discards the type and value of the left operand therefore the return type of + * the function is @c int but this overload will not be available if @c t.item does not compile + * (e.g., there is no such member). The presence of @c t.item also prevents this compilation check + * from happening until overload selection is needed. Therefore if the goal was a function that + * would return the value of the @c T::count member if present and 0 if not, the code would be + * + * @code + * template auto Get_Count(T && t, CaseTag<0>) + * -> int + * { return 0; } + * template auto Get_Count(T && t, CaseTag<1>) + * -> decltype(t.count, int()) + * { return t.count; } + * int Get_Count(Thing& t) { return GetCount(t, CaseArg); } + * @endcode + * + * Note the overloads will be checked from the highest case to the lowest and the first one that + * is valid (via SFINAE) is used. This is the point of using the case arguments, to force an + * ordering on the overload selection. Unfortunately this means the functions @b must be + * templated, even if there's no other reason for it, because it depends on SFINAE which doesn't + * apply to normal overloads. + * + * The key point is the expression in the @c decltype should be the same expression used in the + * method to verify it will compile. It is annoying to type it twice but there's not a better + * option. + * + * Note @c decltype does not accept explicit types - to have the type of "int" an @c int must be + * constructed. This is easy for builtin types except @c void. @c CaseVoidFunc is provided for that + * situation, e.g. decltype(CaseVoidFunc()) provides @c void via @c decltype. + */ + + /// Case hierarchy. + template struct CaseTag : public CaseTag { + constexpr CaseTag() {} + static constexpr unsigned value = N; + }; + + /// Anchor the hierarchy. + template <> struct CaseTag<0> { + constexpr CaseTag() {} + static constexpr unsigned value = 0; + }; + + /** This is the final case - it forces the super class hierarchy. + * After defining the cases using the indexed case arguments, this is used to to perform the call. + * To increase the hierarchy depth, change the template argument to a larger number. + */ + static constexpr CaseTag<9> CaseArg{}; + + // A function to provide a @c void type for use in cases. + // Other types can use the default constructor, e.g. "int()" for @c int. + inline void + CaseVoidFunc() + { + } +} // namespace meta +} // namespace ts diff --git a/src/tscore/Makefile.am b/src/tscore/Makefile.am index 2221bb76b4b..60b37ac634e 100644 --- a/src/tscore/Makefile.am +++ b/src/tscore/Makefile.am @@ -273,6 +273,7 @@ test_tscore_SOURCES = \ unit_tests/test_Scalar.cc \ unit_tests/test_scoped_resource.cc \ unit_tests/test_ts_file.cc \ + unit_tests/test_ts_meta.cc \ unit_tests/test_Vec.cc CompileParseRules_SOURCES = CompileParseRules.cc diff --git a/src/tscore/unit_tests/test_ts_meta.cc b/src/tscore/unit_tests/test_ts_meta.cc new file mode 100644 index 00000000000..35a6d765c04 --- /dev/null +++ b/src/tscore/unit_tests/test_ts_meta.cc @@ -0,0 +1,106 @@ +/** @file + + Unit tests for ts_meta.h and other meta programming. + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one or more contributor license + agreements. See the NOTICE file distributed with this work for additional information regarding + copyright ownership. The ASF licenses this file to you 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 + +#include "tscore/ts_meta.h" + +#include "catch.hpp" + +struct A { + int _value; +}; + +struct AA : public A { +}; + +struct B { + std::string _value; +}; + +struct C { +}; + +struct D { +}; + +// Some example meta-programming, saving it for possible later use. + +template struct is_any_of_1 { + static constexpr bool value = false; +}; +template struct is_any_of_1 { + static constexpr bool value = std::is_same::value; +}; +template struct is_any_of_1 { + static constexpr bool value = std::is_same::value || (sizeof...(Rest) > 0 && is_any_of_1::value); +}; + +// Requires C++17 +template struct is_any_of_2 { + static constexpr bool value = std::disjunction...>::value; +}; + +TEST_CASE("Meta Example", "[meta][example]") +{ + REQUIRE(is_any_of_1::value); + REQUIRE(!is_any_of_1::value); + REQUIRE(is_any_of_1::value); + REQUIRE(!is_any_of_1::value); + REQUIRE(!is_any_of_1::value); + + REQUIRE(is_any_of_2::value); + REQUIRE(!is_any_of_2::value); + REQUIRE(is_any_of_2::value); + REQUIRE(!is_any_of_2::value); + REQUIRE(!is_any_of_2::value); +} + +// Start of ts::meta testing. + +namespace +{ +template +auto +detect(T &&t, ts::meta::CaseTag<0>) -> std::string_view +{ + return "none"; +} +template +auto +detect(T &&t, ts::meta::CaseTag<1>) -> decltype(t._value, std::string_view()) +{ + return "value"; +} +template +std::string_view +detect(T &&t) +{ + return detect(t, ts::meta::CaseArg); +} +} // namespace + +TEST_CASE("Meta", "[meta]") +{ + REQUIRE(detect(A()) == "value"); + REQUIRE(detect(B()) == "value"); + REQUIRE(detect(C()) == "none"); + REQUIRE(detect(AA()) == "value"); +}