From 0f39bff82276574c4b6c9337b1502ad275bc5b50 Mon Sep 17 00:00:00 2001 From: "Jonathan B. Coe" Date: Wed, 5 Nov 2025 16:32:09 -1000 Subject: [PATCH 01/11] Add Appendix listing code from National Body comment author --- proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md | 72 ++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md b/proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md index 4a5a8330..ec085eaa 100644 --- a/proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md +++ b/proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md @@ -113,3 +113,75 @@ associated `indirect` instance(s) must not be in a valueless state. * Add clarification that the authors would like to see implementation and **usage** experience to motivate the introduction of a `reference_wrapper`-like API. + +## Appendix + +Taken from the National Body Comment author's variant library (OneOf) there is a type `indirection`, +similar to `indirect` and reproduced below. + +`indirection` has a reference-wrapper-like API but has much more scope for undefined behaviour than +`indirect` as accepted into the C++26 working draft. + +Copy construction, move construction, assignment and move assignment of `indirection` all require that +`other` has not been moved-from. `operator T&` and `get` (const and non-const qualified) all require that +`this` has not been moved from. As designed, `indirection` gives a user no way to check that an instance +of `indirection` has not been moved from. + + + +``` +template +using disable_capturing = + std::enable_if_t>::value, + int>; + +template +struct indirection +{ +public: + indirection() : p_(new T{}) {} + + indirection(indirection&& other) noexcept : p_(other.p_) { + other.p_ = nullptr; + } + + indirection& operator=(indirection&& other) noexcept { + this->~indirection(); + return *::new ((void*)this) indirection{ std::move(other) }; + } + + indirection(indirection const& other) : indirection(other.get()) {} + + indirection& operator=(indirection const& other) { + if (p_) { + get() = other.get(); + } else { + indirection tmp{ other }; + swap(*this, tmp); + } + + return *this; + } + + template = 0> + explicit indirection(A&& a, As&&... as) + : p_(new T(std::forward(a), std::forward(as)...)) { + } + + ~indirection() { delete p_; } + + friend void swap(indirection& x, indirection& y) noexcept { + std::swap(x.p_, y.p_); + } + + operator T&() { return this->get(); } + operator T const&() const { return this->get(); } + + T& get() { return *p_; } + T const& get() const { return *p_; } + +private: + T* p_; +}; +``` From ee77e99a716d38cb4166d36d145d4f850648a2bf Mon Sep 17 00:00:00 2001 From: "Jonathan B. Coe" Date: Wed, 5 Nov 2025 16:46:53 -1000 Subject: [PATCH 02/11] Fix formatting of sample code --- proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md | 71 +++++++++++----------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md b/proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md index ec085eaa..a36b60d8 100644 --- a/proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md +++ b/proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md @@ -136,52 +136,51 @@ using disable_capturing = int>; template -struct indirection -{ -public: - indirection() : p_(new T{}) {} +struct indirection { + public: + indirection() : p_(new T{}) {} - indirection(indirection&& other) noexcept : p_(other.p_) { - other.p_ = nullptr; - } + indirection(indirection&& other) noexcept : p_(other.p_) { + other.p_ = nullptr; + } - indirection& operator=(indirection&& other) noexcept { - this->~indirection(); - return *::new ((void*)this) indirection{ std::move(other) }; - } + indirection& operator=(indirection&& other) noexcept { + this->~indirection(); + return *::new ((void*)this) indirection{std::move(other)}; + } - indirection(indirection const& other) : indirection(other.get()) {} + indirection(indirection const& other) : indirection(other.get()) {} - indirection& operator=(indirection const& other) { - if (p_) { - get() = other.get(); - } else { - indirection tmp{ other }; - swap(*this, tmp); - } + indirection& operator=(indirection const& other) { + if (p_) { + get() = other.get(); + } else { + indirection tmp{other}; + swap(*this, tmp); + } - return *this; - } + return *this; + } - template = 0> - explicit indirection(A&& a, As&&... as) - : p_(new T(std::forward(a), std::forward(as)...)) { - } + template = 0> + explicit indirection(A&& a, As&&... as) + : p_(new T(std::forward(a), std::forward(as)...)) {} - ~indirection() { delete p_; } + ~indirection() { delete p_; } - friend void swap(indirection& x, indirection& y) noexcept { - std::swap(x.p_, y.p_); - } + friend void swap(indirection& x, indirection& y) noexcept { + std::swap(x.p_, y.p_); + } - operator T&() { return this->get(); } - operator T const&() const { return this->get(); } + operator T&() { return this->get(); } - T& get() { return *p_; } - T const& get() const { return *p_; } + operator T const&() const { return this->get(); } -private: - T* p_; + T& get() { return *p_; } + + T const& get() const { return *p_; } + + private: + T* p_; }; ``` From 5a1de16f5c4a26f9de254619b5e89bb1360b0da6 Mon Sep 17 00:00:00 2001 From: "Jonathan B. Coe" Date: Thu, 6 Nov 2025 15:15:32 -1000 Subject: [PATCH 03/11] Add example and discussion of recursive variants --- exploration/BUILD.bazel | 17 +++++ exploration/recursive_variant.cc | 1 + exploration/recursive_variant.h | 38 ++++++++++ exploration/recursive_variant_test.cc | 48 +++++++++++++ proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md | 83 ++++++++++++++++++++++ 5 files changed, 187 insertions(+) create mode 100644 exploration/recursive_variant.cc create mode 100644 exploration/recursive_variant.h create mode 100644 exploration/recursive_variant_test.cc diff --git a/exploration/BUILD.bazel b/exploration/BUILD.bazel index 37ae3622..9f9b5b0d 100644 --- a/exploration/BUILD.bazel +++ b/exploration/BUILD.bazel @@ -33,3 +33,20 @@ build_test( name = "traits_and_concepts_build_test", targets = ["traits_and_concepts"], ) + +cc_library( + name = "recursive_variant", + srcs = ["recursive_variant.cc"], + hdrs = ["recursive_variant.h"], + visibility = ["//visibility:private"], + deps = ["//:indirect"], +) + +cc_test( + name = "recursive_variant_test", + srcs = ["recursive_variant_test.cc"], + deps = [ + "recursive_variant", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/exploration/recursive_variant.cc b/exploration/recursive_variant.cc new file mode 100644 index 00000000..d7f929be --- /dev/null +++ b/exploration/recursive_variant.cc @@ -0,0 +1 @@ +#include "recursive_variant.h" diff --git a/exploration/recursive_variant.h b/exploration/recursive_variant.h new file mode 100644 index 00000000..e126f303 --- /dev/null +++ b/exploration/recursive_variant.h @@ -0,0 +1,38 @@ +// An investigation of recursive variants and an associated helper type. +#include +#include + +#include "indirect.h" + +namespace xyz::testing { + +template +struct overload : Ts... { + using Ts::operator()...; + + overload(Ts&&... ts) : Ts(std::forward(ts))... {} +}; + +template +struct deref { + T t_; + + using U = decltype(*std::declval()); + + operator U&() { return *t_; } + + operator const U&() const { return *t_; } +}; + +template +overload(Ts...) -> overload; + +struct ASTNode; +using ASTNodeRecursiveStorage = deref>; +using ASTNodeData = std::variant; + +struct ASTNode { + ASTNodeData data_; +}; + +} // namespace xyz::testing diff --git a/exploration/recursive_variant_test.cc b/exploration/recursive_variant_test.cc new file mode 100644 index 00000000..7fde8493 --- /dev/null +++ b/exploration/recursive_variant_test.cc @@ -0,0 +1,48 @@ +#include "recursive_variant.h" + +#include + +namespace { + +using xyz::testing::ASTNode; +using xyz::testing::deref; +using xyz::testing::overload; + +TEST(RecursiveVariant, ExplicitAccess) { + ASTNode node; + + // This is a pain to write and exposes implementation details. + int result = + std::visit(overload([](const int&) { return 0; }, // + [](const std::string&) { return 1; }, // + [](const ASTNodeRecursiveStorage&) { return 2; }), + node.data_); + + EXPECT_EQ(result, 0); +} + +TEST(RecursiveVariant, DerefAccess) { + ASTNode node; + + // This is nicer to write. + int result = std::visit(overload([](const int&) { return 0; }, // + [](const std::string&) { return 1; }, // + [](const ASTNode&) { return 2; }), + node.data_); + + EXPECT_EQ(result, 0); +} + +TEST(RecursiveVariant, LazyAccess) { + ASTNode node; + + // This is lazy. + int result = std::visit(overload([](const int&) { return 0; }, // + [](const std::string&) { return 1; }, // + [](const auto&) { return 2; }), + node.data_); + + EXPECT_EQ(result, 0); +} + +} // namespace diff --git a/proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md b/proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md index a36b60d8..ec707deb 100644 --- a/proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md +++ b/proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md @@ -184,3 +184,86 @@ struct indirection { T* p_; }; ``` + +The variant in OneOf uses `indirection` to implement recursive variants. + +A recursive variant can be implemented with `indirect` but `indirect`'s absence +of an implicit conversion to a reference type means that a pointer-like interface is +exposed to users. + +Improvements to the usability of recursive variants could be made by introducing another type to automatically dereference indirect storage. We illustrate the use of a helper type below. + +Any design to better support recursive variants would be best addressed with a concrete proposal +and the authors of indirect invite the author of the National Body Comment to put forward a paper +for the C++2029 standard. + +``` +template +struct deref { + T t_; + + using U = decltype(*std::declval()); + + operator U&() { return *t_; } + + operator const U&() const { return *t_; } +}; + +struct ASTNode; +using ASTNodeRecursiveStorage = deref>; +using ASTNodeData = std::variant; + +struct ASTNode { + ASTNodeData data_; +}; + +/// Access tests. + +template +struct overload : Ts... { + using Ts::operator()...; + + overload(Ts&&... ts) : Ts(std::forward(ts))... {} +}; + +template +overload(Ts...) -> overload; + +TEST(RecursiveVariant, ExplicitAccess) { + ASTNode node; + + // This is a pain to write and exposes implementation details. + int result = + std::visit(overload([](const int&) { return 0; }, // + [](const std::string&) { return 1; }, // + [](const ASTNodeRecursiveStorage&) { return 2; }), + node.data_); + + EXPECT_EQ(result, 0); +} + +TEST(RecursiveVariant, DerefAccess) { + ASTNode node; + + // This is nicer to write. + int result = std::visit(overload([](const int&) { return 0; }, // + [](const std::string&) { return 1; }, // + [](const ASTNode&) { return 2; }), + node.data_); + + EXPECT_EQ(result, 0); +} + +TEST(RecursiveVariant, LazyAccess) { + ASTNode node; + + // This is lazy. + int result = std::visit(overload([](const int&) { return 0; }, // + [](const std::string&) { return 1; }, // + [](const auto&) { return 2; }), + node.data_); + + EXPECT_EQ(result, 0); +} + +``` From c17e89967ddfc167ea467b8d10fd3e5651a8cff9 Mon Sep 17 00:00:00 2001 From: "Jonathan B. Coe" Date: Thu, 6 Nov 2025 15:18:18 -1000 Subject: [PATCH 04/11] Fix code organisation --- MODULE.bazel.lock | 33 +-------------------------------- exploration/recursive_variant.h | 6 +++--- 2 files changed, 4 insertions(+), 35 deletions(-) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 741c4267..b723bfe9 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -152,37 +152,6 @@ }, "selectedYankedVersions": {}, "moduleExtensions": { - "@@apple_support+//crosstool:setup.bzl%apple_cc_configure_extension": { - "general": { - "bzlTransitiveDigest": "E970FlMbwpgJPdPUQzatKh6BMfeE0ZpWABvwshh7Tmg=", - "usagesDigest": "aYRVMk+1OupIp+5hdBlpzT36qgd6ntgSxYTzMLW5K4U=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "local_config_apple_cc_toolchains": { - "repoRuleId": "@@apple_support+//crosstool:setup.bzl%_apple_cc_autoconf_toolchains", - "attributes": {} - }, - "local_config_apple_cc": { - "repoRuleId": "@@apple_support+//crosstool:setup.bzl%_apple_cc_autoconf", - "attributes": {} - } - }, - "recordedRepoMappingEntries": [ - [ - "apple_support+", - "bazel_tools", - "bazel_tools" - ], - [ - "bazel_tools", - "rules_cc", - "rules_cc+" - ] - ] - } - }, "@@rules_fuzzing+//fuzzing/private:extensions.bzl%non_module_dependencies": { "general": { "bzlTransitiveDigest": "mGiTB79hRNjmeDTQdzkpCHyzXhErMbufeAmySBt7s5s=", @@ -268,7 +237,7 @@ }, "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { "general": { - "bzlTransitiveDigest": "hUTp2w+RUVdL7ma5esCXZJAFnX7vLbVfLd7FwnQI6bU=", + "bzlTransitiveDigest": "OlvsB0HsvxbR8ZN+J9Vf00X/+WVz/Y/5Xrq2LgcVfdo=", "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, diff --git a/exploration/recursive_variant.h b/exploration/recursive_variant.h index e126f303..05504304 100644 --- a/exploration/recursive_variant.h +++ b/exploration/recursive_variant.h @@ -13,6 +13,9 @@ struct overload : Ts... { overload(Ts&&... ts) : Ts(std::forward(ts))... {} }; +template +overload(Ts...) -> overload; + template struct deref { T t_; @@ -24,9 +27,6 @@ struct deref { operator const U&() const { return *t_; } }; -template -overload(Ts...) -> overload; - struct ASTNode; using ASTNodeRecursiveStorage = deref>; using ASTNodeData = std::variant; From f46d3726fd70385139cdbb182d3a270155cc2fb3 Mon Sep 17 00:00:00 2001 From: "Jonathan B. Coe" Date: Thu, 6 Nov 2025 15:20:47 -1000 Subject: [PATCH 05/11] Code cleanup --- exploration/recursive_variant.h | 3 --- exploration/recursive_variant_test.cc | 1 + proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md | 3 --- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/exploration/recursive_variant.h b/exploration/recursive_variant.h index 05504304..a82f834a 100644 --- a/exploration/recursive_variant.h +++ b/exploration/recursive_variant.h @@ -13,9 +13,6 @@ struct overload : Ts... { overload(Ts&&... ts) : Ts(std::forward(ts))... {} }; -template -overload(Ts...) -> overload; - template struct deref { T t_; diff --git a/exploration/recursive_variant_test.cc b/exploration/recursive_variant_test.cc index 7fde8493..21ea1508 100644 --- a/exploration/recursive_variant_test.cc +++ b/exploration/recursive_variant_test.cc @@ -5,6 +5,7 @@ namespace { using xyz::testing::ASTNode; +using xyz::testing::ASTNodeRecursiveStorage; using xyz::testing::deref; using xyz::testing::overload; diff --git a/proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md b/proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md index ec707deb..afb47fe7 100644 --- a/proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md +++ b/proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md @@ -226,9 +226,6 @@ struct overload : Ts... { overload(Ts&&... ts) : Ts(std::forward(ts))... {} }; -template -overload(Ts...) -> overload; - TEST(RecursiveVariant, ExplicitAccess) { ASTNode node; From ddea9808a8385ef517ddfd2faedc73cd4178d2ee Mon Sep 17 00:00:00 2001 From: "Jonathan B. Coe" Date: Thu, 6 Nov 2025 15:28:23 -1000 Subject: [PATCH 06/11] Add changelog --- proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md b/proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md index afb47fe7..0a61e7d1 100644 --- a/proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md +++ b/proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md @@ -105,6 +105,10 @@ J. B. Coe, A. Peacock, and S. Parent, 2023 \ ## History +### Changes in R2 + +Add Appendix discussing recursive variants. + ### Changes in R1 * Add clarification that `operator->` and `operator*` (including const-qualified and @@ -114,7 +118,7 @@ associated `indirect` instance(s) must not be in a valueless state. * Add clarification that the authors would like to see implementation and **usage** experience to motivate the introduction of a `reference_wrapper`-like API. -## Appendix +## Appendix: Recursive variants and `OneOf` Taken from the National Body Comment author's variant library (OneOf) there is a type `indirection`, similar to `indirect` and reproduced below. From a88addcd0fee8fe174baa38b14c8da758a353962 Mon Sep 17 00:00:00 2001 From: "Jonathan B. Coe" Date: Thu, 6 Nov 2025 15:30:51 -1000 Subject: [PATCH 07/11] Fix typo --- proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md b/proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md index 0a61e7d1..d790c864 100644 --- a/proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md +++ b/proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md @@ -199,7 +199,7 @@ Improvements to the usability of recursive variants could be made by introducing Any design to better support recursive variants would be best addressed with a concrete proposal and the authors of indirect invite the author of the National Body Comment to put forward a paper -for the C++2029 standard. +for the C++29 standard. ``` template From 009a4ea97542ecd6908d2b496cfe7afa2efb5250 Mon Sep 17 00:00:00 2001 From: "Jonathan B. Coe" Date: Thu, 6 Nov 2025 15:48:34 -1000 Subject: [PATCH 08/11] Update date --- proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md b/proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md index d790c864..6dda958b 100644 --- a/proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md +++ b/proposals/DRAFT_NO_IMPLICIT_CONVERSIONS.md @@ -8,7 +8,7 @@ D3902R2 Working Group: Library Evolution -Date: 2025-11-3 +Date: 2025-11-6 _Jonathan Coe \<\>_ From 686ad4a50279a3842eae3649220049eea2e37137 Mon Sep 17 00:00:00 2001 From: "Jonathan B. Coe" Date: Thu, 6 Nov 2025 15:52:50 -1000 Subject: [PATCH 09/11] Reorganise code --- exploration/recursive_variant.h | 7 ------- exploration/recursive_variant_test.cc | 8 +++++++- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/exploration/recursive_variant.h b/exploration/recursive_variant.h index a82f834a..8c567c10 100644 --- a/exploration/recursive_variant.h +++ b/exploration/recursive_variant.h @@ -6,13 +6,6 @@ namespace xyz::testing { -template -struct overload : Ts... { - using Ts::operator()...; - - overload(Ts&&... ts) : Ts(std::forward(ts))... {} -}; - template struct deref { T t_; diff --git a/exploration/recursive_variant_test.cc b/exploration/recursive_variant_test.cc index 21ea1508..09de4bda 100644 --- a/exploration/recursive_variant_test.cc +++ b/exploration/recursive_variant_test.cc @@ -7,7 +7,13 @@ namespace { using xyz::testing::ASTNode; using xyz::testing::ASTNodeRecursiveStorage; using xyz::testing::deref; -using xyz::testing::overload; + +template +struct overload : Ts... { + using Ts::operator()...; + + overload(Ts&&... ts) : Ts(std::forward(ts))... {} +}; TEST(RecursiveVariant, ExplicitAccess) { ASTNode node; From 809967b44d8c16cfe3865f9d08b52f601a42d3cc Mon Sep 17 00:00:00 2001 From: "Jonathan B. Coe" Date: Thu, 6 Nov 2025 16:36:37 -1000 Subject: [PATCH 10/11] Add missing header guards --- exploration/incomplete_types.h | 5 +++++ exploration/recursive_variant.h | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/exploration/incomplete_types.h b/exploration/incomplete_types.h index 2f961e3d..aaf24279 100644 --- a/exploration/incomplete_types.h +++ b/exploration/incomplete_types.h @@ -1,5 +1,8 @@ // Wrapper types with members templated on an incomplete type. +#ifndef XYZ_EXPLORATION_INCOMPLETE_TYPES_H_ +#define XYZ_EXPLORATION_INCOMPLETE_TYPES_H_ + #include #include #include @@ -70,3 +73,5 @@ struct VariantVectorMember { bool operator==(const VariantVectorMember& other) const; }; } // namespace xyz::testing + +#endif // XYZ_EXPLORATION_INCOMPLETE_TYPES_H_ diff --git a/exploration/recursive_variant.h b/exploration/recursive_variant.h index 8c567c10..50996b1a 100644 --- a/exploration/recursive_variant.h +++ b/exploration/recursive_variant.h @@ -1,4 +1,8 @@ // An investigation of recursive variants and an associated helper type. + +#ifndef XYZ_EXPLORATION_RECURSIVE_VARIANT_H_ +#define XYZ_EXPLORATION_RECURSIVE_VARIANT_H_ + #include #include @@ -26,3 +30,5 @@ struct ASTNode { }; } // namespace xyz::testing + +#endif // XYZ_EXPLORATION_RECURSIVE_VARIANT_H_ From bae0c36447b74fbb684bb77b79512b8499242212 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Nov 2025 02:49:11 +0000 Subject: [PATCH 11/11] Add CMake configuration for recursive_variant_test Add CMake configuration for recursive_variant_test Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --- exploration/CMakeLists.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/exploration/CMakeLists.txt b/exploration/CMakeLists.txt index 7e09808a..ae42b060 100644 --- a/exploration/CMakeLists.txt +++ b/exploration/CMakeLists.txt @@ -8,6 +8,15 @@ if (${XYZ_VALUE_TYPES_IS_NOT_SUBPROJECT}) incomplete_types_test.cc ) + xyz_add_test( + NAME recursive_variant_test + LINK_LIBRARIES indirect + FILES + recursive_variant.h + recursive_variant.cc + recursive_variant_test.cc + ) + add_library(traits_and_concepts OBJECT) target_sources(traits_and_concepts PRIVATE