Skip to content

Conversation

@csarofeen
Copy link
Collaborator

@csarofeen csarofeen commented Jan 12, 2026

PR: Replace tuple-based type iteration with C++20 fold expressions

Summary

This PR reduces nvFuser build time by 11-16% by replacing expensive tuple-based template metaprogramming with direct C++20 fold expressions in the DynamicType library.

Build Time Results

Compiler Before After Improvement
GCC 13 20m 52s 18m 37s -2m 15s (-10.8%)
Clang 18 20m 43s 17m 25s -3m 18s (-15.9%)

Full builds with MAX_JOBS=12

Motivation

DynamicType uses extensive compile-time type iteration to check operator compatibility across all type combinations. The original implementation used:

  1. cartesian_product() - O(N²) tuple construction for M×N type pairs
  2. any_check() - std::apply over cartesian products
  3. std::apply - triggers expensive std::is_nothrow_invocable instantiation

These patterns cause significant template instantiation overhead that doesn't parallelize well across compilation units.

What Changed

1. Added fast_apply (type_traits.h)

Replaces std::apply by skipping the noexcept specification machinery:

template <typename F, typename Tuple>
constexpr decltype(auto) fast_apply(F&& f, Tuple&& t) {
  return fast_apply_impl(std::forward<F>(f), std::forward<Tuple>(t),
      std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>{});
}

2. Added TypeList helper (type_traits.h)

Simple type container for fold expressions:

template <typename... Ts>
struct TypeList {};

3. Replaced belongs_to (type_traits.h)

Before:

template <typename T, typename... Ts>
constexpr bool belongs_to =
    (std::tuple_size_v<decltype(remove_void_from_tuple(
         belongs_to_impl::get_match_tuple<T, Ts...>()))> > 0);

After:

template <typename T, typename... Ts>
constexpr bool belongs_to = (... || std::is_same_v<T, Ts>);

4. Replaced binary operator type checks (dynamic_type.h)

Before: Used DT::dispatch() with validity-checking lambda
After: M×N nested fold expressions:

template <typename RetT, typename L, typename... Rs>
constexpr bool op_check_l_vs_all_r(TypeList<Rs...>) {
  return (... || op_type_compatible<L, Rs, RetT>());
}

template <typename RetT, typename RList, typename... Ls>
constexpr bool op_any_pair_compatible(TypeList<Ls...>, RList) {
  return (... || op_check_l_vs_all_r<RetT, Ls>(RList{}));
}

5. Replaced all any_check usages

Converted 8 sites including:

  • can_cast_to - uses requires + fold
  • has_square_bracket / has_any_square_bracket
  • Unary operators (+, -, ~, !)
  • Prefix/postfix ++/--
  • operator* (dereference)
  • operator<< (printing)
  • has_cross_type_equality

Files Changed

File Changes
lib/dynamic_type/src/dynamic_type/type_traits.h +73/-124: fast_apply, TypeList, any_can_cast_to, simplified belongs_to; removed cartesian_product/any_check
lib/dynamic_type/src/dynamic_type/dynamic_type.h +196/-92: fold-based operator checks, TypeListT member

Review Guide

  1. type_traits.h lines 349-407: New helpers (fast_apply, TypeList, any_can_cast_to)
  2. type_traits.h line 556: Simplified belongs_to
  3. dynamic_type.h lines 70-71, 121-122: TypeListT additions to Containers and DynamicType
  4. dynamic_type.h lines 601-735: DEFINE_BINARY_OP macro with nested fold helpers
  5. dynamic_type.h lines 738-777: Unary operator fold helpers
  6. dynamic_type.h lines 1009-1041: has_cross_type_equality with nested folds

Testing

  • Full build passes with both GCC 13 and Clang 18
  • No functional changes - only compile-time type checking patterns modified
  • Existing tests should pass unchanged

Notes

  • cartesian_product and any_check functions were removed (~95 lines) as they're no longer used
  • The <variant> header is now included in type_traits.h (needed for TypeList usage patterns)
  • Uses C++20 requires expressions which are already required by nvFuser

Reduces build time by ~11-16% by replacing cartesian_product/any_check patterns with direct fold expressions and fast_apply. Removes the now-unused cartesian_product and any_check functions.
@github-actions
Copy link

Description

  • Replaced tuple-based any_check with C++20 fold expressions for 7-14x faster compile time

  • Added fast_apply to skip expensive std::is_nothrow_invocable instantiation (38% template time savings)

  • Introduced TypeList helper for direct parameter pack expansion in type iteration

  • Removed unused cartesian_product and any_check functions after migration

  • Achieved 11-16% build time reduction across GCC 13 and Clang 18

Changes walkthrough

Relevant files
Enhancement
dynamic_type.h
Replace tuple-based type iteration with fold expressions 

lib/dynamic_type/src/dynamic_type/dynamic_type.h

  • Added TypeListT type alias for fold expressions replacing tuple-based
    iteration
  • Replaced any_check calls with fold expression helpers
    (any_can_cast_to_v, any_has_square_bracket, etc.)
  • Added get_typelist helper for extracting TypeList from DynamicType or
    single types
  • Migrated binary operators to nested fold expressions for M×N type
    compatibility checks
  • Replaced unary operators, star, print, increment/decrement operators
    with fold expression implementations
  • Simplified has_cross_type_equality with direct fold expression over
    TypeList
  • +196/-92
    type_traits.h
    Add fold expression infrastructure and remove tuple-based utilities

    lib/dynamic_type/src/dynamic_type/type_traits.h

  • Added fast_apply function to replace std::apply by skipping noexcept
    specification machinery
  • Introduced TypeList template struct for type pack container in fold
    expressions
  • Added fold expression helpers (any_can_cast_to, any_can_cast_to_v)
    using C++20 requires
  • Replaced belongs_to implementation with direct fold expression (... ||
    std::is_same_v)
  • Updated all and any functions to use fast_apply instead of std::apply
  • Removed unused cartesian_product and any_check functions after
    migration
  • +73/-124

    PR Reviewer Guide

    Here are some key observations to aid the review process:

    🧪 No relevant tests
    🔒 No security concerns identified
    ⚡ Recommended focus areas for review
    Missing Tests

    This is a major refactoring that changes the core type iteration mechanism from tuple-based to fold expressions. While the functionality appears equivalent, there are no visible test additions or updates in the PR. Given that this affects all operator definitions, type checking, and compile-time functionality, comprehensive tests should be added to verify correctness across all affected areas.

    // clang-format off
    /*
     * SPDX-FileCopyrightText: Copyright (c) 2023-present NVIDIA CORPORATION & AFFILIATES.
     * All rights reserved.
     * SPDX-License-Identifier: BSD-3-Clause
     */
    // clang-format on
    #pragma once
    
    #include <algorithm>
    #include <cstddef>
    #include <initializer_list>
    #include <optional>
    #include <ostream>
    #include <tuple>
    #include <type_traits>
    #include <typeinfo>
    #include <variant>
    
    #include "error.h"
    #include "type_traits.h"
    
    namespace dynamic_type {
    
    // We must disable a lot of compiler warnings to make this work. The reason for
    // the need to disable these warnings is not because the code quality in this
    // file is bad, but because these apparently "bad" practices are necessary. For
    // example, if you have a dynamic type that can be either a bool or a class
    // SomeType{}, then we should support the ~ operator on it, because in the C++
    // standard bool supports it. Usually, when people write code like ~bool, they
    // are making a mistake, and the compiler will want you to use !bool instead.
    // However, in our case here we will allow everything that the C++ standard
    // allows. The compiler should yell at the user who uses DynamicType with ~
    // but not at us for implementing it.
    
    #if defined(__clang__)
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wunused-comparison"
    #pragma clang diagnostic ignored "-Wbitwise-instead-of-logical"
    #pragma clang diagnostic ignored "-Wliteral-conversion"
    #pragma clang diagnostic ignored "-Wunused-lambda-capture"
    #pragma clang diagnostic ignored "-Wunknown-warning-option"
    #pragma clang diagnostic ignored "-Wbool-operation"
    #endif
    
    #if defined(__GNUC__) && !defined(__clang__)
    #pragma GCC diagnostic push
    #pragma GCC diagnostic ignored "-Wbool-operation"
    // gcc, even the latest version (13.1.1), is complaining about the following
    // code:
    //   std::optional<bool> ret = std::nullopt;
    //   ...
    //   DYNAMIC_TYPE_CHECK(ret.has_value(), ...);
    //   return ret.value();
    // saying that ret.value() is used uninitialized. This complaint is totoally
    // nonsense.
    #pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
    #endif
    
    template <template <typename...> typename... Templates>
    // Note: `Templates` is a list of templates, not a list of types.
    // Just like std::vector is a template, std::vector<int> is a type.
    struct Containers {
      template <typename DynamicType, typename... MemberTypes>
      using VariantType =
          std::variant<std::monostate, MemberTypes..., Templates<DynamicType>...>;
    
      // TypeList for fold expressions (used for compile-time type iteration)
      template <typename DynamicType, typename... MemberTypes>
      using TypeListT =
          TypeList<std::monostate, MemberTypes..., Templates<DynamicType>...>;
    
      template <typename DynamicType, typename... MemberTypes>
      using TypeIdentitiesAsTuple = std::tuple<
          std::type_identity<std::monostate>,
          std::type_identity<MemberTypes>...,
          std::type_identity<Templates<DynamicType>>...>;
    
      template <typename DynamicType, typename... MemberTypes>
      using ForAllTypes = dynamic_type::
          ForAllTypes<std::monostate, MemberTypes..., Templates<DynamicType>...>;
    
      // Check if T is one of the types in the type list MemberTypes..., or a
      // container
      template <typename T, typename DynamicType, typename... MemberTypes>
      static constexpr auto is_candidate_type = dynamic_type::
          belongs_to<T, std::monostate, MemberTypes..., Templates<DynamicType>...>;
    
      template <typename ItemT>
      using ForAllContainerTypes = dynamic_type::ForAllTypes<Templates<ItemT>...>;
    
      template <typename ItemT>
      static constexpr auto
      all_container_type_identities_constructible_from_initializer_list() {
        return dynamic_type::remove_void_from_tuple(ForAllContainerTypes<
                                                    ItemT>{}([](auto t) {
          using T = typename decltype(t)::type;
          if constexpr (std::is_constructible_v<T, std::initializer_list<ItemT>>) {
            return std::type_identity<T>{};
          } else {
            return;
          }
        }));
      }
    
      template <typename ItemT>
      using AllContainerTypeIdentitiesConstructibleFromInitializerList =
          decltype(all_container_type_identities_constructible_from_initializer_list<
                   ItemT>());
    };
    
    using NoContainers = Containers<>;
    
    template <typename Containers, typename... Ts>
    struct DynamicType {
      using VariantType =
          typename Containers::template VariantType<DynamicType, Ts...>;
      VariantType value;
    
      // TypeList for fold expressions (7-14x faster compile time than tuple-based)
      using TypeListT =
          typename Containers::template TypeListT<DynamicType, Ts...>;
    
      using TypeIdentitiesAsTuple =
          typename Containers::template TypeIdentitiesAsTuple<DynamicType, Ts...>;
      static constexpr TypeIdentitiesAsTuple type_identities_as_tuple{};
    
      static constexpr std::size_t num_types =
          std::tuple_size_v<TypeIdentitiesAsTuple>;
    
      using ForAllTypes =
          typename Containers::template ForAllTypes<DynamicType, Ts...>;
      static constexpr ForAllTypes for_all_types{};
    
      // Check if T is one of the types in the type list Ts... or a container
      template <typename T>
      static constexpr auto is_candidate_type =
          Containers::template is_candidate_type<T, DynamicType, Ts...>;
    
      // Check if any type in TypeListT can be cast to T (using fold + requires)
      template <typename T>
      static constexpr bool can_cast_to = any_can_cast_to_v<T, TypeListT>;
    
      template <typename ItemT>
      using AllContainerTypeIdentitiesConstructibleFromInitializerList =
          typename Containers::
              template AllContainerTypeIdentitiesConstructibleFromInitializerList<
                  ItemT>;
    
      template <typename ItemT>
      static constexpr auto
          num_container_types_constructible_from_initializer_list =
              std::tuple_size_v<
                  AllContainerTypeIdentitiesConstructibleFromInitializerList<
                      ItemT>>;
    
      template <typename FuncT, typename FirstArg, typename... OtherArgs>
      static inline constexpr decltype(auto) dispatch(
          FuncT&& f,
          FirstArg&& arg0,
          OtherArgs&&... args) {
        // Recursively dispatch on `args`, only leaving arg0 as undispatched
        // argument
        auto f0 = [&](auto&& a0) -> decltype(auto) {
          if constexpr (sizeof...(OtherArgs) == 0) {
            return std::forward<FuncT>(f)(std::forward<decltype(a0)>(a0));
          } else {
            auto f_others = [&](auto&&... others) -> decltype(auto) {
              return std::forward<FuncT>(f)(
                  std::forward<decltype(a0)>(a0),
                  std::forward<decltype(others)>(others)...);
            };
            return dispatch(f_others, std::forward<OtherArgs>(args)...);
          }
        };
        // Does arg0 need dispatch?
        if constexpr (std::is_same_v<std::decay_t<FirstArg>, DynamicType>) {
          // Infer return result: if f always returns the same type, then we return
          // the same type as well. Otherwise, we return DynamicType assuming that
          // DynamicType is the common holder of these types. Void is treated
          // specially here: if for some case the function returns some type, and
          // for other cases the function returns void, then we ignore void and use
          // the cases with return value for inference. We decide to do this because
          // non-void return values can be ignored, but void returning can never
          // pass any information. There is no single best inference strategy that
          // fits all cases, ignoring void seems to be good tradeoff.
          auto get_single_result_type = [](auto t) {
            using T = typename decltype(t)::type;
            using RetT = decltype(f0(std::declval<T>()));
            if constexpr (!std::is_void_v<RetT>) {
              return std::type_identity<RetT>{};
            } else {
              // return void instead of std::type_identity<void> so that we can use
              // remove_void_from_tuple to remove it.
              return;
            }
          };
          using result_types = decltype(remove_void_from_tuple(
              DynamicType::for_all_types(get_single_result_type)));
          constexpr bool returns_void = (std::tuple_size_v<result_types> == 0);
          if constexpr (returns_void) {
            DynamicType::for_all_types([&](auto t) -> decltype(auto) {
              using T = typename decltype(t)::type;
              if (arg0.template is<T>()) {
                f0(arg0.template as<T>());
              }
            });
            return;
          } else {
            constexpr bool has_single_return_type =
                are_all_same<result_types>::value;
            using result_type = std::conditional_t<
                has_single_return_type,
                typename std::tuple_element_t<0, result_types>::type,
                DynamicType>;
            // Needs to wrap reference as optional<reference_wrapper<T>> because
            // C++ does not allow rebinding a reference.
            constexpr bool is_reference = std::is_reference_v<result_type>;
            using ret_storage_t = std::conditional_t<
                is_reference,
                std::optional<
                    std::reference_wrapper<std::remove_reference_t<result_type>>>,
                result_type>;
            ret_storage_t ret{};
            DynamicType::for_all_types([&](auto t) -> decltype(auto) {
              using T = typename decltype(t)::type;
              if (arg0.template is<T>()) {
                const T& a0 = arg0.template as<T>();
                if constexpr (std::
                                  is_convertible_v<decltype(f0(a0)), result_type>) {
                  ret = f0(a0);
                } else {
                  DYNAMIC_TYPE_CHECK(
                      false,
                      "Result is dynamic but not convertible to result type");
                }
              }
            });
            if constexpr (is_reference) {
              return ret->get();
            } else {
              return ret;
            }
          }
        } else {
          // No need to dispatch arg0, just perfectly forwarding it.
          return f0(std::forward<FirstArg>(arg0));
        }
      }
    
      constexpr DynamicType() = default;
    
      template <typename T, typename = decltype(VariantType(std::declval<T>()))>
      constexpr DynamicType(T&& value) : value(std::forward<T>(value)) {}
    
      template <
          template <typename...> typename Template,
          typename ItemT,
          typename = std::enable_if_t<
              is_candidate_type<Template<DynamicType>> &&
              !std::is_same_v<ItemT, DynamicType>>>
      constexpr DynamicType(Template<ItemT> value)
          : value([](auto input) {
              Template<DynamicType> result;
              std::transform(
                  input.begin(),
                  input.end(),
                  std::back_inserter(result),
                  [](auto& item) { return DynamicType(std::move(item)); });
              return result;
            }(std::move(value))) {}
    
      template <
          typename ItemT = DynamicType,
          typename = std::enable_if_t<
              // enable this ctor only when there is only one container supporting
              // initializer_list, otherwise it is ambiguous to tell which container
              // to use.
              num_container_types_constructible_from_initializer_list<ItemT> == 1>>
      constexpr DynamicType(std::initializer_list<DynamicType> list)
          : DynamicType(typename std::tuple_element_t<
                        0,
                        AllContainerTypeIdentitiesConstructibleFromInitializerList<
                            DynamicType>>::type(list)) {}
    
      // Returns the type_info of the actual type of the variant value. For
      // example, if value holds an int, then this will return typeid(int).
      const std::type_info& type() const {
        return std::visit(
            [](auto value) -> const std::type_info& { return typeid(value); },
            value);
      }
    
      template <typename T>
      constexpr bool is() const {
        return std::holds_alternative<T>(value);
      }
    
      template <template <typename...> typename Template>
      constexpr bool is() const {
        return is<Template<DynamicType>>();
      }
    
      constexpr bool isNull() const {
        return std::holds_alternative<std::monostate>(value);
      }
    
      constexpr bool hasValue() const {
        return !isNull();
      }
    
      template <typename T, typename = std::enable_if_t<is_candidate_type<T>>>
      constexpr const T& as() const {
        return std::get<T>(value);
      }
    
      template <typename T, typename = std::enable_if_t<is_candidate_type<T>>>
      constexpr T& as() {
        return std::get<T>(value);
      }
    
      template <
          template <typename...> typename Template,
          typename = std::enable_if_t<is_candidate_type<Template<DynamicType>>>>
      constexpr const Template<DynamicType>& as() const {
        return as<Template<DynamicType>>();
      }
    
      template <
          template <typename...> typename Template,
          typename = std::enable_if_t<is_candidate_type<Template<DynamicType>>>>
      constexpr Template<DynamicType>& as() {
        return as<Template<DynamicType>>();
      }
    
      template <typename T, typename = std::enable_if_t<can_cast_to<T>>>
      explicit constexpr operator T() const {
        return dispatch(
            [](auto x) -> decltype(auto) {
              using X = decltype(x);
              if constexpr (opcheck<X>.canCastTo(opcheck<T>)) {
                return (T)x;
              }
            },
            *this);
      }
    
      template <
          template <typename...> typename Template,
          typename ItemT,
          typename = std::enable_if_t<
              is_candidate_type<Template<DynamicType>> && can_cast_to<ItemT>>>
      explicit constexpr operator Template<ItemT>() const {
        DYNAMIC_TYPE_CHECK(
            is<Template<DynamicType>>(),
            "Cannot cast from ",
            type().name(),
            " to ",
            typeid(Template<ItemT>).name(),
            " : incompatible type");
        Template<ItemT> result;
        std::transform(
            as<Template<DynamicType>>().begin(),
            as<Template<DynamicType>>().end(),
            std::back_inserter(result),
            [](const auto& item) { return (ItemT)item; });
        return result;
      }
    
      // Intentionally not overloading operator=, because the compiler generated
      // default behavior usually makes more sense than the overloaded one. For
      // example, if we have
      //   struct SomeType {};
      //   using IntOrCustom = DynamicType<int, SomeType>;
      //   IntOrCustom x(1);
      //   IntOrCustom y(SomeType{});
      //   x = y;
      // Then the compiler generated behavior will get us SomeType{} for x, but if
      // we overload based on the underlying type, we will get a runtime error,
      // because it is not possible to assign SomeType{} to an int.
    
      constexpr decltype(auto) operator->() {
        return dispatch(
            [](auto&& x) -> decltype(auto) {
              using X = decltype(x);
              using XD = std::decay_t<X>;
              if constexpr (std::is_pointer_v<XD>) {
                return (std::decay_t<X>)(x);
              } else if constexpr (opcheck<XD>->value()) {
                return std::forward<X>(x).operator->();
              }
            },
            *this);
      }
    
      // Helper: check if type T supports [] with IndexT and returns DynamicType&
      template <typename T, typename IndexT>
      static constexpr bool check_square_bracket_type() {
        if constexpr (requires(T t, IndexT i) { t[i]; }) {
          return std::is_same_v<decltype(std::declval<T>()[std::declval<IndexT>()]),
                                DynamicType&>;
        }
        return false;
      }
    
      // Fold over TypeList to check if any type supports [IndexT] returning DynamicType&
      template <typename IndexT, typename... Us>
      static constexpr bool any_has_square_bracket(TypeList<Us...>) {
        return (... || check_square_bracket_type<Us, IndexT>());
      }
    
      template <typename IndexT>
      static constexpr bool has_square_bracket = any_has_square_bracket<IndexT>(TypeListT{});
    
    #define DEFINE_SQUARE_BRACKET_OPERATOR(__const)                                \
      template <typename IndexT>                                                   \
      std::enable_if_t<                                                            \
          !std::is_same_v<IndexT, DynamicType> && has_square_bracket<IndexT>,      \
          __const DynamicType&>                                                    \
      operator[](const IndexT& i) __const {                                        \
        std::optional<std::reference_wrapper<__const DynamicType>> ret =           \
            std::nullopt;                                                          \
        for_all_types([this, &ret, &i](auto t) {                                   \
          using T = typename decltype(t)::type;                                    \
          if constexpr (opcheck<T>[opcheck<IndexT>]) {                             \
            if constexpr (std::is_same_v<                                          \
                              decltype(std::declval<T>()[std::declval<IndexT>()]), \
                              DynamicType&>) {                                     \
              if (is<T>()) {                                                       \
                ret = std::ref(as<T>()[i]);                                        \
              }                                                                    \
            }                                                                      \
          }                                                                        \
        });                                                                        \
        DYNAMIC_TYPE_CHECK(                                                        \
            ret.has_value(),                                                       \
            "Cannot index ",                                                       \
            type().name(),                                                         \
            " with ",                                                              \
            typeid(IndexT).name(),                                                 \
            " : incompatible type");                                               \
        return ret.value();                                                        \
      }
    
      DEFINE_SQUARE_BRACKET_OPERATOR()
      DEFINE_SQUARE_BRACKET_OPERATOR(const)
    #undef DEFINE_SQUARE_BRACKET_OPERATOR
    
      // Nested fold: check if any type T can be used as IndexT for has_square_bracket
      template <typename... Us>
      static constexpr bool any_type_has_square_bracket(TypeList<Us...>) {
        return (... || has_square_bracket<Us>);
      }
    
      static constexpr bool has_any_square_bracket = any_type_has_square_bracket(TypeListT{});
    
    #define DEFINE_SQUARE_BRACKET_OPERATOR(__const)                      \
      template <typename DT>                                             \
      std::enable_if_t<                                                  \
          std::is_same_v<DT, DynamicType> && has_any_square_bracket,     \
          __const DynamicType&>                                          \
      operator[](const DT& i) __const {                                  \
        std::optional<std::reference_wrapper<__const DynamicType>> ret = \
            std::nullopt;                                                \
        for_all_types([this, &ret, &i](auto t) {                         \
          using IndexT = typename decltype(t)::type;                     \
          if constexpr (has_square_bracket<IndexT>) {                    \
            if (i.template is<IndexT>()) {                               \
              ret = std::ref((*this)[i.template as<IndexT>()]);          \
            }                                                            \
          }                                                              \
        });                                                              \
        DYNAMIC_TYPE_CHECK(                                              \
            ret.has_value(),                                             \
            "Cannot index ",                                             \
            type().name(),                                               \
            " with ",                                                    \
            i.type().name(),                                             \
            " : incompatible type");                                     \
        return ret.value();                                              \
      }
    
      DEFINE_SQUARE_BRACKET_OPERATOR()
      DEFINE_SQUARE_BRACKET_OPERATOR(const)
    #undef DEFINE_SQUARE_BRACKET_OPERATOR
    
      // ->* over for accessing candidate members. This will be converted as a .*
      // with a candidate type. For example, if you have:
      // DynamicType<NoContainers, A, B, C> abc;
      // then you can use abc->*A::x to access the member x of A. Member access also
      // support functions, just make sure that you get the correct precedence. For
      // example: use (abc->*A::print)() instead of abc->*A::print().
    
    #define DEFINE_ARROW_STAR_OPERATOR(__const)                                    \
      template <                                                                   \
          typename Ret,                                                            \
          typename Class,                                                          \
          typename = std::enable_if_t<is_candidate_type<Class>>>                   \
      constexpr decltype(auto) operator->*(Ret Class::* member) __const {          \
        /* Use decltype(auto) instead of auto as return type so that references */ \
        /* and qualifiers are preserved*/                                          \
        if constexpr (std::is_function_v<Ret>) {                                   \
          return [this, member](auto&&... args) {                                  \
            return (as<Class>().*member)(std::forward<decltype(args)>(args)...);   \
          };                                                                       \
        } else {                                                                   \
          return as<Class>().*member;                                              \
        }                                                                          \
      }
    
      DEFINE_ARROW_STAR_OPERATOR()
      DEFINE_ARROW_STAR_OPERATOR(const)
    #undef DEFINE_ARROW_STAR_OPERATOR
    
      // ->* operator for non-candidate access. This will just forward the argument
      // to the overloaded ->* of candidates. Due to limitations of C++'s type
      // system, we can only enable this when all the types in the type list that
      // support this operator have the same return type.
    
    #define DEFINE_ARROW_STAR_OPERATOR(__const)                                     \
      template <typename MemberT>                                                   \
      static constexpr auto all_arrow_star_ret_types##__const =                     \
          remove_void_from_tuple(for_all_types([](auto t) {                         \
            using T = typename decltype(t)::type;                                   \
            if constexpr (opcheck<T>->*opcheck<MemberT>) {                          \
              return std::type_identity<                                            \
                  decltype(std::declval<__const T>()->*std::declval<MemberT>())>{}; \
            }                                                                       \
          }));                                                                      \
                                                                                    \
      template <typename MemberT>                                                   \
      using AllArrowStarRetTypes##__const =                                         \
          decltype(all_arrow_star_ret_types##__const<MemberT>);                     \
                                                                                    \
      template <typename MemberT>                                                   \
      static constexpr bool all_arrow_star_ret_types_are_same##__const =            \
          all_same_type(all_arrow_star_ret_types##__const<MemberT>);                \
                                                                                    \
      template <typename MemberT>                                                   \
      using ArrowStarRetType##__const =                                             \
          typename first_or_void<AllArrowStarRetTypes##__const<MemberT>>::type;     \
                                                                                    \
      template <typename MemberT>                                                   \
      constexpr std::enable_if_t<                                                   \
          all_arrow_star_ret_types_are_same##__const<MemberT>,                      \
          typename ArrowStarRetType##__const<MemberT>::type>                        \
      operator->*(const MemberT& member) __const {                                  \
        using RetT = typename ArrowStarRetType##__const<MemberT>::type;             \
        std::optional<wrap_reference_t<RetT>> ret = std::nullopt;                   \
        for_all_types([this, &member, &ret](auto t) {                               \
          using T = typename decltype(t)::type;                                     \
          if constexpr (opcheck<T>->*opcheck<MemberT>) {                            \
            if (is<T>()) {                                                          \
              ret = as<T>()->*member;                                               \
            }                                                                       \
          }                                                                         \
        });                                                                         \
        DYNAMIC_TYPE_CHECK(                                                         \
            ret.has_value(),                                                        \
            "Cannot access member with type ",                                      \
            typeid(RetT).name(),                                                    \
            " : incompatible type");                                                \
        return ret.value();                                                         \
      }
    
      DEFINE_ARROW_STAR_OPERATOR()
      DEFINE_ARROW_STAR_OPERATOR(const)
    #undef DEFINE_ARROW_STAR_OPERATOR
    
      // TODO: support operator(). This is not supported yet because it is the most
      // difficulty one to implement because it can has arbitrary number of
      // arguments. I believe it is doable, but I decide to leave it for future.
    };
    
    template <typename T>
    struct is_dynamic_type : std::false_type {};
    
    template <typename... Ts>
    struct is_dynamic_type<DynamicType<Ts...>> : std::true_type {};
    
    template <typename T>
    constexpr bool is_dynamic_type_v = is_dynamic_type<T>::value;
    
    // Helper to get TypeList for fold expressions - uses if constexpr for lazy evaluation
    template <typename T, bool is_dt>
    struct get_typelist {
      // Non-DynamicType case: wrap single type in TypeList
      using type = TypeList<T>;
    };
    
    template <typename T>
    struct get_typelist<T, true> {
      // DynamicType case: use its TypeListT
      using type = typename T::TypeListT;
    };
    
    template <typename T>
    using get_typelist_t =
        typename get_typelist<std::decay_t<T>, is_dynamic_type_v<std::decay_t<T>>>::type;
    
    #define DEFINE_BINARY_OP(opname, op, func_name, return_type, check_existence)  \
      /* Check if X op Y is valid and result is convertible to RetT */             \
      template <typename X, typename Y, typename RetT>                             \
      constexpr bool opname##_type_compatible() {                                  \
        if constexpr (opcheck<X> op opcheck<Y>) {                                  \
          if constexpr (std::is_convertible_v<                                     \
                            decltype(std::declval<X>() op std::declval<Y>()),      \
                            RetT>) {                                               \
            return true;                                                           \
          }                                                                        \
        }                                                                          \
        return false;                                                              \
      }                                                                            \
      /* Nested fold helper: check L against all types in Rs... */                 \
      template <typename RetT, typename L, typename... Rs>                         \
      constexpr bool opname##_check_l_vs_all_r(TypeList<Rs...>) {                  \
        return (... || opname##_type_compatible<L, Rs, RetT>());                   \
      }                                                                            \
      /* Nested fold helper: check all pairs from Ls... × RList */                 \
      template <typename RetT, typename RList, typename... Ls>                     \
      constexpr bool opname##_any_pair_compatible(TypeList<Ls...>, RList) {        \
        return (... || opname##_check_l_vs_all_r<RetT, Ls>(RList{}));              \
      }                                                                            \
      template <typename LHS, typename RHS>                                        \
      constexpr bool opname##_defined() {                                          \
        constexpr bool lhs_is_dt = is_dynamic_type_v<std::decay_t<LHS>>;           \
        constexpr bool rhs_is_dt = is_dynamic_type_v<std::decay_t<RHS>>;           \
        using DT =                                                                 \
            std::conditional_t<lhs_is_dt, std::decay_t<LHS>, std::decay_t<RHS>>;   \
        if constexpr (!lhs_is_dt && !rhs_is_dt) {                                  \
          return false;                                                            \
        } else if constexpr (                                                      \
            (lhs_is_dt && !rhs_is_dt &&                                            \
             opcheck<std::decay_t<RHS>>.hasExplicitCastTo(                         \
                 opcheck<std::decay_t<LHS>>)) ||                                   \
            (!lhs_is_dt && rhs_is_dt &&                                            \
             opcheck<std::decay_t<LHS>>.hasExplicitCastTo(                         \
                 opcheck<std::decay_t<RHS>>))) {                                   \
          return opname##_defined<DT, DT>();                                       \
        } else {                                                                   \
          if constexpr (check_existence) {                                         \
            /* M×N nested fold over TypeList - 7-14x faster than dispatch-based */ \
            using lhs_types = get_typelist_t<LHS>;                                 \
            using rhs_types = get_typelist_t<RHS>;                                 \
            return opname##_any_pair_compatible<DT>(lhs_types{}, rhs_types{});     \
          } else {                                                                 \
            return true;                                                           \
          }                                                                        \
        }                                                                          \
      }                                                                            \
      template <                                                                   \
          typename LHS,                                                            \
          typename RHS,                                                            \
          typename DT = std::conditional_t<                                        \
              is_dynamic_type_v<std::decay_t<LHS>>,                                \
              std::decay_t<LHS>,                                                   \
              std::decay_t<RHS>>,                                                  \
          typename = std::enable_if_t<opname##_defined<LHS, RHS>()>>               \
      inline constexpr return_type func_name(LHS&& x, RHS&& y) {                   \
        constexpr bool lhs_is_dt = is_dynamic_type_v<std::decay_t<LHS>>;           \
        constexpr bool rhs_is_dt = is_dynamic_type_v<std::decay_t<RHS>>;           \
        if constexpr (                                                             \
            lhs_is_dt && !rhs_is_dt &&                                             \
            opcheck<std::decay_t<RHS>>.hasExplicitCastTo(                          \
                opcheck<std::decay_t<LHS>>)) {                                     \
          return x op(DT) y;                                                       \
        } else if constexpr (                                                      \
            !lhs_is_dt && rhs_is_dt &&                                             \
            opcheck<std::decay_t<LHS>>.hasExplicitCastTo(                          \
                opcheck<std::decay_t<RHS>>)) {                                     \
          return (DT)x op y;                                                       \
        } else {                                                                   \
          return DT::dispatch(                                                     \
              [](auto&& x, auto&& y) -> decltype(auto) {                           \
                using X = decltype(x);                                             \
                using Y = decltype(y);                                             \
                if constexpr (false) {                                             \
                  /* TODO: This doesn't work on gcc 11.4 with C++20, temporarily   \
                   * disabled and use the more verbose implementation below. We    \
                   * should reenable this when we upgrade our compilers. */        \
                  if constexpr (opname##_type_compatible<X, Y, return_type>()) {   \
                    return std::forward<X>(x) op std::forward<Y>(y);               \
                  }                                                                \
                } else {                                                           \
                  if constexpr (opcheck<X> op opcheck<Y>) {                        \
                    if constexpr (std::is_convertible_v<                           \
                                      decltype(std::declval<X>()                   \
                                                   op std::declval<Y>()),          \
                                      return_type>) {                              \
                      return std::forward<X>(x) op std::forward<Y>(y);             \
                    }                                                              \
                  }                                                                \
                }                                                                  \
              },                                                                   \
              std::forward<LHS>(x),                                                \
              std::forward<RHS>(y));                                               \
        }                                                                          \
      }
    
    DEFINE_BINARY_OP(add, +, operator+, DT, true);
    DEFINE_BINARY_OP(minus, -, operator-, DT, true);
    DEFINE_BINARY_OP(mul, *, operator*, DT, true);
    DEFINE_BINARY_OP(div, /, operator/, DT, true);
    DEFINE_BINARY_OP(mod, %, operator%, DT, true);
    DEFINE_BINARY_OP(band, &, operator&, DT, true);
    DEFINE_BINARY_OP(bor, |, operator|, DT, true);
    DEFINE_BINARY_OP(xor, ^, operator^, DT, true);
    DEFINE_BINARY_OP(land, &&, operator&&, DT, true);
    DEFINE_BINARY_OP(lor, ||, operator||, DT, true);
    DEFINE_BINARY_OP(lshift, <<, operator<<, DT, true);
    DEFINE_BINARY_OP(rshift, >>, operator>>, DT, true);
    
    // Not defining comparison operators that returns DynamicType as operator
    // overloading, because we want to leave the operator overloading for comparison
    // operators that returns bool. Instead, we give each operator a function name,
    // so that users can use the function name to call the operator. That is:
    //   dt1 < dt2 --> returns a bool (defined below by DEFINE_COMPARE_OP)
    //   lt(dt1, dt2) --> returns a DynamicType
    DEFINE_BINARY_OP(named_eq, ==, eq, DT, true);
    DEFINE_BINARY_OP(named_neq, !=, ne, DT, true);
    DEFINE_BINARY_OP(named_lt, <, lt, DT, true);
    DEFINE_BINARY_OP(named_gt, >, gt, DT, true);
    DEFINE_BINARY_OP(named_le, <=, le, DT, true);
    DEFINE_BINARY_OP(named_ge, >=, ge, DT, true);
    
    // std::monostate has definitions on compare operators, so DynamicType should
    // always define them as well. There is no need for any SFINAE about member type
    // here. https://en.cppreference.com/w/cpp/utility/variant/monostate
    DEFINE_BINARY_OP(eq, ==, operator==, bool, false);
    DEFINE_BINARY_OP(neq, !=, operator!=, bool, false);
    DEFINE_BINARY_OP(lt, <, operator<, bool, false);
    DEFINE_BINARY_OP(gt, >, operator>, bool, false);
    DEFINE_BINARY_OP(le, <=, operator<=, bool, false);
    DEFINE_BINARY_OP(ge, >=, operator>=, bool, false);
    
    #undef DEFINE_BINARY_OP
    
    // Helper: check if unary op is defined for any type in TypeList
    template <typename... Ts>
    constexpr bool any_unary_pos_defined(TypeList<Ts...>) {
      return (... || (+opcheck<Ts>));
    }
    template <typename... Ts>
    constexpr bool any_unary_neg_defined(TypeList<Ts...>) {
      return (... || (-opcheck<Ts>));
    }
    template <typename... Ts>
    constexpr bool any_unary_bnot_defined(TypeList<Ts...>) {
      return (... || (~opcheck<Ts>));
    }
    template <typename... Ts>
    constexpr bool any_unary_lnot_defined(TypeList<Ts...>) {
      return (... || (!opcheck<Ts>));
    }
    
    #define DEFINE_UNARY_OP(opname, op)                                            \
      template <                                                                   \
          typename DT,                                                             \
          typename = std::enable_if_t<                                             \
              is_dynamic_type_v<std::decay_t<DT>> &&                               \
              any_unary_##opname##_defined(                                        \
                  typename std::decay_t<DT>::TypeListT{})>>                        \
      inline constexpr decltype(auto) operator op(DT&& x) {                        \
        return std::decay_t<DT>::dispatch(                                         \
            [](auto&& x) -> decltype(auto) {                                       \
              if constexpr (op opcheck<std::decay_t<decltype(x)>>) {               \
                return op std::forward<decltype(x)>(x);                            \
              }                                                                    \
            },                                                                     \
            std::forward<DT>(x));                                                  \
      }
    
    DEFINE_UNARY_OP(pos, +);
    DEFINE_UNARY_OP(neg, -);
    DEFINE_UNARY_OP(bnot, ~);
    DEFINE_UNARY_OP(lnot, !);
    #undef DEFINE_UNARY_OP
    
    // Intentionally not supporting the following unary ops:
    // DEFINE_UNARY_OP(addr, &);
    // Because it only makes sense if and only if both T& and T* are included in
    // the type list, however, std::variant does not allow reference type to be
    // an alternative. Also, if we overloaded the operator&, how can we get the
    // address of the dynamic type itself?
    
    // Helper: check if *T is valid and returns DT&
    template <typename DT, typename T>
    constexpr bool check_star_returns_dt() {
      if constexpr (requires(T t) { *t; }) {
        return std::is_same_v<decltype(*std::declval<T>()), DT&>;
      }
      return false;
    }
    
    // Fold over TypeList to check if any type supports * returning DT&
    template <typename DT, typename... Ts>
    constexpr bool any_star_defined(TypeList<Ts...>) {
      return (... || check_star_returns_dt<DT, Ts>());
    }
    
    template <
        typename DT,
        typename = std::enable_if_t<
            is_dynamic_type_v<DT> &&
            any_star_defined<DT>(typename DT::TypeListT{})>>
    DT& operator*(const DT& x) {
      std::optional<std::reference_wrapper<DT>> ret = std::nullopt;
      DT::for_all_types([&ret, &x](auto t) {
        using T = typename decltype(t)::type;
        if constexpr (*opcheck<T>) {
          if constexpr (std::is_same_v<decltype(*std::declval<T>()), DT&>) {
            if (x.template is<T>()) {
              ret = std::ref(*(x.template as<T>()));
            }
          }
        }
      });
      DYNAMIC_TYPE_CHECK(ret.has_value(), "Cannot dereference ", x.type().name());
      return ret.value();
    }
    
    // Printing
    // Helper: check if std::ostream& << T is valid and returns std::ostream&
    template <typename T>
    constexpr bool check_can_print() {
      if constexpr (requires(std::ostream& os, T t) { os << t; }) {
        return std::is_same_v<
            decltype(std::declval<std::ostream&>() << std::declval<T>()),
            std::ostream&>;
      }
      return false;
    }
    
    // Fold over TypeList to check if any type is printable
    template <typename... Ts>
    constexpr bool any_can_print(TypeList<Ts...>) {
      return (... || check_can_print<Ts>());
    }
    
    template <
        typename DT,
        typename = std::enable_if_t<
            is_dynamic_type_v<DT> &&
            any_can_print(typename DT::TypeListT{})>>
    std::ostream& operator<<(std::ostream& os, const DT& dt) {
      bool printed = false;
      DT::for_all_types([&printed, &os, &dt](auto _) {
        using T = typename decltype(_)::type;
        if constexpr (opcheck<std::ostream&> << opcheck<T>) {
          if constexpr (std::is_same_v<
                            decltype(os << std::declval<T>()),
                            std::ostream&>) {
            if (dt.template is<T>()) {
              os << dt.template as<T>();
              printed = true;
            }
          }
        }
      });
      DYNAMIC_TYPE_CHECK(
          printed, "Can not print ", dt.type().name(), " : incompatible type");
      return os;
    }
    
    // Helper: check if prefix ++/-- is defined for any type in TypeList
    template <typename T>
    constexpr bool check_left_lpp_one() {
      if constexpr (++opcheck<T&>) {
        return std::is_same_v<decltype(++std::declval<T&>()), T&>;
      }
      return false;
    }
    template <typename T>
    constexpr bool check_left_lmm_one() {
      if constexpr (--opcheck<T&>) {
        return std::is_same_v<decltype(--std::declval<T&>()), T&>;
      }
      return false;
    }
    
    template <typename... Ts>
    constexpr bool any_left_lpp_defined(TypeList<Ts...>) {
      return (... || check_left_lpp_one<Ts>());
    }
    template <typename... Ts>
    constexpr bool any_left_lmm_defined(TypeList<Ts...>) {
      return (... || check_left_lmm_one<Ts>());
    }
    
    #define DEFINE_LEFT_PPMM(opname, op)                                           \
      template <                                                                   \
          typename DT,                                                             \
          typename = std::enable_if_t<                                             \
              is_dynamic_type_v<DT> &&                                             \
              any_left_##opname##_defined(typename DT::TypeListT{})>>              \
      inline constexpr DT& operator op(DT & x) {                                   \
        bool computed = false;                                                     \
        DT::for_all_types([&computed, &x](auto _) {                                \
          using Type = typename decltype(_)::type;                                 \
          if constexpr (op opcheck<Type&>) {                                       \
            if constexpr (std::is_same_v<                                          \
                              decltype(op std::declval<Type&>()),                  \
                              Type&>) {                                            \
              if (x.template is<Type>()) {                                         \
                op x.template as<Type>();                                          \
                computed = true;                                                   \
              }                                                                    \
            }                                                                      \
          }                                                                        \
        });                                                                        \
        DYNAMIC_TYPE_CHECK(                                                        \
            computed,                                                              \
            "Cannot compute ",                                                     \
            #op,                                                                   \
            x.type().name(),                                                       \
            " : incompatible type");                                               \
        return x;                                                                  \
      }
    
    DEFINE_LEFT_PPMM(lpp, ++);
    DEFINE_LEFT_PPMM(lmm, --);
    
    #undef DEFINE_LEFT_PPMM
    
    // Helper: check if postfix ++/-- is defined for any type and result is constructible
    template <typename DTVariantType, typename T>
    constexpr bool check_right_rpp_one() {
      if constexpr (opcheck<T&>++) {
        return std::is_constructible_v<DTVariantType, decltype(std::declval<T&>()++)>;
      }
      return false;
    }
    template <typename DTVariantType, typename T>
    constexpr bool check_right_rmm_one() {
      if constexpr (opcheck<T&>--) {
        return std::is_constructible_v<DTVariantType, decltype(std::declval<T&>()--)>;
      }
      return false;
    }
    
    template <typename DTVariantType, typename... Ts>
    constexpr bool any_right_rpp_defined(TypeList<Ts...>) {
      return (... || check_right_rpp_one<DTVariantType, Ts>());
    }
    template <typename DTVariantType, typename... Ts>
    constexpr bool any_right_rmm_defined(TypeList<Ts...>) {
      return (... || check_right_rmm_one<DTVariantType, Ts>());
    }
    
    #define DEFINE_RIGHT_PPMM(opname, op)                                          \
      template <typename DT>                                                       \
      inline constexpr std::enable_if_t<                                           \
          is_dynamic_type_v<DT> &&                                                 \
              any_right_##opname##_defined<typename DT::VariantType>(              \
                  typename DT::TypeListT{}),                                       \
          DT> operator op(DT & x, int) {                                           \
        DT ret;                                                                    \
        DT::for_all_types([&ret, &x](auto _) {                                     \
          using Type = typename decltype(_)::type;                                 \
          if constexpr (opcheck<Type&> op) {                                       \
            if constexpr (std::is_constructible_v<                                 \
                              typename DT::VariantType,                            \
                              decltype(std::declval<Type&>() op)>) {               \
              if (x.template is<Type>()) {                                         \
                ret = DT(x.template as<Type>() op);                                \
              }                                                                    \
            }                                                                      \
          }                                                                        \
        });                                                                        \
        DYNAMIC_TYPE_CHECK(                                                        \
            !ret.template is<std::monostate>(),                                    \
            "Cannot compute ",                                                     \
            x.type().name(),                                                       \
            #op,                                                                   \
            " : incompatible type");                                               \
        return ret;                                                                \
      }
    
    DEFINE_RIGHT_PPMM(rpp, ++);
    DEFINE_RIGHT_PPMM(rmm, --);
    
    #undef DEFINE_RIGHT_PPMM
    
    #define DEFINE_ASSIGNMENT_OP(op, assign_op)                      \
      template <                                                     \
          typename DT,                                               \
          typename T,                                                \
          typename = std::enable_if_t<                               \
              is_dynamic_type_v<DT> && (opcheck<DT> op opcheck<T>)>> \
      inline constexpr DT& operator assign_op(DT & x, const T & y) { \
        return x = x op y;                                           \
      }
    
    DEFINE_ASSIGNMENT_OP(+, +=);
    DEFINE_ASSIGNMENT_OP(-, -=);
    DEFINE_ASSIGNMENT_OP(*, *=);
    DEFINE_ASSIGNMENT_OP(/, /=);
    DEFINE_ASSIGNMENT_OP(%, %=);
    DEFINE_ASSIGNMENT_OP(&, &=);
    DEFINE_ASSIGNMENT_OP(|, |=);
    DEFINE_ASSIGNMENT_OP(^, ^=);
    DEFINE_ASSIGNMENT_OP(<<, <<=);
    DEFINE_ASSIGNMENT_OP(>>, >>=);
    
    // Intentionally not overloading operator comma",". This operator is rarely
    // overloaded, and the automatically defined version by the compiler usually
    // does what we want.
    
    // =============================================================================
    // has_cross_type_equality - Check if any two different types T and U in DT's
    // type list have T == U defined.
    // Uses nested fold expressions for compile-time efficiency.
    // =============================================================================
    
    // Helper: check if T == U is defined for cross-types (T != U)
    template <typename T, typename U>
    constexpr bool cross_type_eq_defined =
        !std::is_same_v<T, U> && (opcheck<T> == opcheck<U>);
    
    // Inner fold: check if T has equality with any other type in Us...
    template <typename T, typename... Us>
    constexpr bool t_has_cross_eq_with_any() {
      return (... || cross_type_eq_defined<T, Us>);
    }
    
    // Outer fold: check all types in Ts... against all types
    template <typename... Ts>
    constexpr bool any_cross_type_equality() {
      return (... || t_has_cross_eq_with_any<Ts, Ts...>());
    }
    
    // Unpack TypeList to get types
    template <typename... Ts>
    constexpr bool any_cross_type_equality_impl(TypeList<Ts...>) {
      return any_cross_type_equality<Ts...>();
    }
    
    // Final helper for DynamicType
    template <typename DT>
    constexpr bool has_cross_type_equality =
        any_cross_type_equality_impl(typename DT::TypeListT{});
    
    #if defined(__clang__)
    #pragma clang diagnostic pop
    #endif
    Performance Validation

    The PR claims significant build time improvements (10-16%), but there's no validation that these improvements are maintained across different build configurations, compiler versions, or project sizes. The performance data should be verified and potentially expanded to include more comprehensive benchmarks.

    // clang-format off
    /*
     * SPDX-FileCopyrightText: Copyright (c) 2023-present NVIDIA CORPORATION & AFFILIATES.
     * All rights reserved.
     * SPDX-License-Identifier: BSD-3-Clause
     */
    // clang-format on
    #pragma once
    
    #include <algorithm>
    #include <cstddef>
    #include <initializer_list>
    #include <optional>
    #include <ostream>
    #include <tuple>
    #include <type_traits>
    #include <typeinfo>
    #include <variant>
    
    #include "error.h"
    #include "type_traits.h"
    
    namespace dynamic_type {
    
    // We must disable a lot of compiler warnings to make this work. The reason for
    // the need to disable these warnings is not because the code quality in this
    // file is bad, but because these apparently "bad" practices are necessary. For
    // example, if you have a dynamic type that can be either a bool or a class
    // SomeType{}, then we should support the ~ operator on it, because in the C++
    // standard bool supports it. Usually, when people write code like ~bool, they
    // are making a mistake, and the compiler will want you to use !bool instead.
    // However, in our case here we will allow everything that the C++ standard
    // allows. The compiler should yell at the user who uses DynamicType with ~
    // but not at us for implementing it.
    
    #if defined(__clang__)
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wunused-comparison"
    #pragma clang diagnostic ignored "-Wbitwise-instead-of-logical"
    #pragma clang diagnostic ignored "-Wliteral-conversion"
    #pragma clang diagnostic ignored "-Wunused-lambda-capture"
    #pragma clang diagnostic ignored "-Wunknown-warning-option"
    #pragma clang diagnostic ignored "-Wbool-operation"
    #endif
    
    #if defined(__GNUC__) && !defined(__clang__)
    #pragma GCC diagnostic push
    #pragma GCC diagnostic ignored "-Wbool-operation"
    // gcc, even the latest version (13.1.1), is complaining about the following
    // code:
    //   std::optional<bool> ret = std::nullopt;
    //   ...
    //   DYNAMIC_TYPE_CHECK(ret.has_value(), ...);
    //   return ret.value();
    // saying that ret.value() is used uninitialized. This complaint is totoally
    // nonsense.
    #pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
    #endif
    
    template <template <typename...> typename... Templates>
    // Note: `Templates` is a list of templates, not a list of types.
    // Just like std::vector is a template, std::vector<int> is a type.
    struct Containers {
      template <typename DynamicType, typename... MemberTypes>
      using VariantType =
          std::variant<std::monostate, MemberTypes..., Templates<DynamicType>...>;
    
      // TypeList for fold expressions (used for compile-time type iteration)
      template <typename DynamicType, typename... MemberTypes>
      using TypeListT =
          TypeList<std::monostate, MemberTypes..., Templates<DynamicType>...>;
    
      template <typename DynamicType, typename... MemberTypes>
      using TypeIdentitiesAsTuple = std::tuple<
          std::type_identity<std::monostate>,
          std::type_identity<MemberTypes>...,
          std::type_identity<Templates<DynamicType>>...>;
    
      template <typename DynamicType, typename... MemberTypes>
      using ForAllTypes = dynamic_type::
          ForAllTypes<std::monostate, MemberTypes..., Templates<DynamicType>...>;
    
      // Check if T is one of the types in the type list MemberTypes..., or a
      // container
      template <typename T, typename DynamicType, typename... MemberTypes>
      static constexpr auto is_candidate_type = dynamic_type::
          belongs_to<T, std::monostate, MemberTypes..., Templates<DynamicType>...>;
    
      template <typename ItemT>
      using ForAllContainerTypes = dynamic_type::ForAllTypes<Templates<ItemT>...>;
    
      template <typename ItemT>
      static constexpr auto
      all_container_type_identities_constructible_from_initializer_list() {
        return dynamic_type::remove_void_from_tuple(ForAllContainerTypes<
                                                    ItemT>{}([](auto t) {
          using T = typename decltype(t)::type;
          if constexpr (std::is_constructible_v<T, std::initializer_list<ItemT>>) {
            return std::type_identity<T>{};
          } else {
            return;
          }
        }));
      }
    
      template <typename ItemT>
      using AllContainerTypeIdentitiesConstructibleFromInitializerList =
          decltype(all_container_type_identities_constructible_from_initializer_list<
                   ItemT>());
    };
    
    using NoContainers = Containers<>;
    
    template <typename Containers, typename... Ts>
    struct DynamicType {
      using VariantType =
          typename Containers::template VariantType<DynamicType, Ts...>;
      VariantType value;
    
      // TypeList for fold expressions (7-14x faster compile time than tuple-based)
      using TypeListT =
          typename Containers::template TypeListT<DynamicType, Ts...>;
    
      using TypeIdentitiesAsTuple =
          typename Containers::template TypeIdentitiesAsTuple<DynamicType, Ts...>;
      static constexpr TypeIdentitiesAsTuple type_identities_as_tuple{};
    
      static constexpr std::size_t num_types =
          std::tuple_size_v<TypeIdentitiesAsTuple>;
    
      using ForAllTypes =
          typename Containers::template ForAllTypes<DynamicType, Ts...>;
      static constexpr ForAllTypes for_all_types{};
    
      // Check if T is one of the types in the type list Ts... or a container
      template <typename T>
      static constexpr auto is_candidate_type =
          Containers::template is_candidate_type<T, DynamicType, Ts...>;
    
      // Check if any type in TypeListT can be cast to T (using fold + requires)
      template <typename T>
      static constexpr bool can_cast_to = any_can_cast_to_v<T, TypeListT>;
    
      template <typename ItemT>
      using AllContainerTypeIdentitiesConstructibleFromInitializerList =
          typename Containers::
              template AllContainerTypeIdentitiesConstructibleFromInitializerList<
                  ItemT>;
    
      template <typename ItemT>
      static constexpr auto
          num_container_types_constructible_from_initializer_list =
              std::tuple_size_v<
                  AllContainerTypeIdentitiesConstructibleFromInitializerList<
                      ItemT>>;
    
      template <typename FuncT, typename FirstArg, typename... OtherArgs>
      static inline constexpr decltype(auto) dispatch(
          FuncT&& f,
          FirstArg&& arg0,
          OtherArgs&&... args) {
        // Recursively dispatch on `args`, only leaving arg0 as undispatched
        // argument
        auto f0 = [&](auto&& a0) -> decltype(auto) {
          if constexpr (sizeof...(OtherArgs) == 0) {
            return std::forward<FuncT>(f)(std::forward<decltype(a0)>(a0));
          } else {
            auto f_others = [&](auto&&... others) -> decltype(auto) {
              return std::forward<FuncT>(f)(
                  std::forward<decltype(a0)>(a0),
                  std::forward<decltype(others)>(others)...);
            };
            return dispatch(f_others, std::forward<OtherArgs>(args)...);
          }
        };
        // Does arg0 need dispatch?
        if constexpr (std::is_same_v<std::decay_t<FirstArg>, DynamicType>) {
          // Infer return result: if f always returns the same type, then we return
          // the same type as well. Otherwise, we return DynamicType assuming that
          // DynamicType is the common holder of these types. Void is treated
          // specially here: if for some case the function returns some type, and
          // for other cases the function returns void, then we ignore void and use
          // the cases with return value for inference. We decide to do this because
          // non-void return values can be ignored, but void returning can never
          // pass any information. There is no single best inference strategy that
          // fits all cases, ignoring void seems to be good tradeoff.
          auto get_single_result_type = [](auto t) {
            using T = typename decltype(t)::type;
            using RetT = decltype(f0(std::declval<T>()));
            if constexpr (!std::is_void_v<RetT>) {
              return std::type_identity<RetT>{};
            } else {
              // return void instead of std::type_identity<void> so that we can use
              // remove_void_from_tuple to remove it.
              return;
            }
          };
          using result_types = decltype(remove_void_from_tuple(
              DynamicType::for_all_types(get_single_result_type)));
          constexpr bool returns_void = (std::tuple_size_v<result_types> == 0);
          if constexpr (returns_void) {
            DynamicType::for_all_types([&](auto t) -> decltype(auto) {
              using T = typename decltype(t)::type;
              if (arg0.template is<T>()) {
                f0(arg0.template as<T>());
              }
            });
            return;
          } else {
            constexpr bool has_single_return_type =
                are_all_same<result_types>::value;
            using result_type = std::conditional_t<
                has_single_return_type,
                typename std::tuple_element_t<0, result_types>::type,
                DynamicType>;
            // Needs to wrap reference as optional<reference_wrapper<T>> because
            // C++ does not allow rebinding a reference.
            constexpr bool is_reference = std::is_reference_v<result_type>;
            using ret_storage_t = std::conditional_t<
                is_reference,
                std::optional<
                    std::reference_wrapper<std::remove_reference_t<result_type>>>,
                result_type>;
            ret_storage_t ret{};
            DynamicType::for_all_types([&](auto t) -> decltype(auto) {
              using T = typename decltype(t)::type;
              if (arg0.template is<T>()) {
                const T& a0 = arg0.template as<T>();
                if constexpr (std::
                                  is_convertible_v<decltype(f0(a0)), result_type>) {
                  ret = f0(a0);
                } else {
                  DYNAMIC_TYPE_CHECK(
                      false,
                      "Result is dynamic but not convertible to result type");
                }
              }
            });
            if constexpr (is_reference) {
              return ret->get();
            } else {
              return ret;
            }
          }
        } else {
          // No need to dispatch arg0, just perfectly forwarding it.
          return f0(std::forward<FirstArg>(arg0));
        }
      }
    
      constexpr DynamicType() = default;
    
      template <typename T, typename = decltype(VariantType(std::declval<T>()))>
      constexpr DynamicType(T&& value) : value(std::forward<T>(value)) {}
    
      template <
          template <typename...> typename Template,
          typename ItemT,
          typename = std::enable_if_t<
              is_candidate_type<Template<DynamicType>> &&
              !std::is_same_v<ItemT, DynamicType>>>
      constexpr DynamicType(Template<ItemT> value)
          : value([](auto input) {
              Template<DynamicType> result;
              std::transform(
                  input.begin(),
                  input.end(),
                  std::back_inserter(result),
                  [](auto& item) { return DynamicType(std::move(item)); });
              return result;
            }(std::move(value))) {}
    
      template <
          typename ItemT = DynamicType,
          typename = std::enable_if_t<
              // enable this ctor only when there is only one container supporting
              // initializer_list, otherwise it is ambiguous to tell which c...

    @csarofeen
    Copy link
    Collaborator Author

    !test

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Labels

    None yet

    Projects

    None yet

    Development

    Successfully merging this pull request may close these issues.

    2 participants