Phase 8b-Lite: pipe-operator chaining for named parameters
Some checks failed
C++ Tests / test-fast (push) Successful in 1m58s
C++ Tests / test-fast (pull_request) Successful in 2m33s
API Docs / doc-build (pull_request) Successful in 51s
C++ Tests / test-cgal (push) Has been skipped
C++ Tests / test-cgal (pull_request) Failing after 10m32s
Some checks failed
C++ Tests / test-fast (push) Successful in 1m58s
C++ Tests / test-fast (pull_request) Successful in 2m33s
API Docs / doc-build (pull_request) Successful in 51s
C++ Tests / test-cgal (push) Has been skipped
C++ Tests / test-cgal (pull_request) Failing after 10m32s
Adds `operator|` in `namespace CGAL` so package-local named parameters
can be combined left-to-right without modifying CGAL upstream:
auto p = CGAL::parameters::gradient_tolerance(1e-12)
| CGAL::parameters::max_iterations(500)
| CGAL::parameters::output_uv_map(uv);
CGAL::discrete_conformal_map_euclidean(mesh, p);
Why not the canonical `.a().b().c()` syntax
───────────────────────────────────────────
CGAL's standard chaining mechanism requires registering each named
parameter as a member function on `Named_function_parameters` via the
`CGAL_add_named_parameter` macro in
`CGAL/STL_Extension/internal/parameters_interface.h` — a vendored
upstream file that conformallab++ deliberately treats as read-only.
Adding member-function chainers for our package-local tags would
require either forking CGAL or modifying the vendored copy. Neither
is acceptable for a library that wants to remain portable across
future CGAL releases.
The pipe-operator achieves the same compositional semantics via a
free function in `namespace CGAL` (so ADL finds it for
`Named_function_parameters` operands). Implementation: rebuild the
right-hand-side `Named_function_parameters` with the left-hand-side
as its `Base`, producing an indistinguishable chain that every entry
function accepts unchanged.
Implementation: `code/include/CGAL/Conformal_map/internal/parameters.h`
lines 158-187. The operator is constrained to right-hand-sides with
`No_property` base (i.e. fresh single-parameter packs from the helper
functions), so it never collides with any future CGAL operator on the
same type.
Tests (2 new, total Phase-8b-Lite suite 15 → 17)
────────────────────────────────────────────────
* CGALPhase8bLite.NamedParamPipe_MultipleParamsTakeEffect
Chain three parameters; verify all three take effect (tight
tolerance respected + UV pmap populated + iteration cap honoured).
* CGALPhase8bLite.NamedParamPipe_TwoParams
Chain two parameters; verify max_iterations(0) blocks the loop
even when combined with another param.
Full CGAL suite: 234/234 PASSED, 0 SKIPPED (was 232).
Total: 257/257 PASSED, 0 SKIPPED (was 255).
scripts/check-test-counts.sh: OK.
Documentation updates
─────────────────────
* doc/tutorials/add-output-uv-map.md §3.4: "Current limitation: no
chaining" → "Chaining: use the pipe operator `|`". Explains why
CGAL's `.member()` syntax isn't available and shows the `|`
workaround with a working code example.
* doc/architecture/locked-vs-flexible.md §8: chaining now flagged as
shipped via pipe; recommended posture says `.member()` chaining
only if a user pushes for the CGAL-canonical syntax.
* doc/roadmap/porting-status.md §5: API limitations table updated.
* doc/api/tests.md: CGALPhase8bLite row 15 → 17, total 232 → 234.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -170,9 +170,71 @@ inline auto normalise_layout(bool flag)
|
||||
}
|
||||
|
||||
/// \}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// Pipe-operator chaining for the Discrete_conformal_map package
|
||||
//
|
||||
// CGAL's standard chaining syntax `a.b(...).c(...)` requires modifying the
|
||||
// CGAL upstream `parameters_interface.h` file, which we deliberately treat
|
||||
// as a read-only vendored dependency. Instead, conformallab++ provides a
|
||||
// pipe-operator overload that achieves the same effect from
|
||||
// left-to-right composition:
|
||||
//
|
||||
// auto p = CGAL::parameters::gradient_tolerance(1e-12)
|
||||
// | CGAL::parameters::max_iterations(500)
|
||||
// | CGAL::parameters::output_uv_map(uv);
|
||||
// CGAL::discrete_conformal_map_euclidean(mesh, p);
|
||||
//
|
||||
// Semantics: `a | b` reads as "first apply a, then b". The result is a
|
||||
// Named_function_parameters chain identical to what `.b()` chained onto
|
||||
// `a` would have produced, so the resulting object is accepted by every
|
||||
// entry function in the package.
|
||||
//
|
||||
// Implementation note: this operator is intentionally placed in the
|
||||
// CGAL::parameters namespace so it is found by ADL when the operands are
|
||||
// `Named_function_parameters` objects produced by the helpers above. We
|
||||
// constrain it to no-base NPs only (i.e. the operands are fresh
|
||||
// single-parameter packs) to avoid colliding with any future CGAL
|
||||
// operator on the same type.
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/*!
|
||||
\addtogroup PkgConformalMapNamedParameters
|
||||
\{
|
||||
*/
|
||||
|
||||
/// \}
|
||||
|
||||
} // namespace parameters
|
||||
|
||||
/// Pipe-operator chaining for package-local named parameters.
|
||||
///
|
||||
/// `a | b` combines `a` and `b` into a single `Named_function_parameters`
|
||||
/// chain. The right-hand side `b` must be a fresh single-parameter pack
|
||||
/// (i.e. its Base is `No_property`) — typically the direct return value
|
||||
/// of one of the helper functions in `CGAL::parameters::*`. The
|
||||
/// left-hand side can be any chain length.
|
||||
///
|
||||
/// Lives in `namespace CGAL` (not `CGAL::parameters`) so ADL finds it
|
||||
/// when the operands are `CGAL::Named_function_parameters<...>` values.
|
||||
///
|
||||
/// Use as a workaround for the missing `a.b().c()` chaining syntax
|
||||
/// while CGAL upstream does not yet expose a per-package extension
|
||||
/// point for member-function chainers.
|
||||
template <typename T_a, typename Tag_a, typename Base_a,
|
||||
typename T_b, typename Tag_b>
|
||||
auto operator|(const CGAL::Named_function_parameters<T_a, Tag_a, Base_a>& a,
|
||||
const CGAL::Named_function_parameters<T_b, Tag_b, CGAL::internal_np::No_property>& b)
|
||||
{
|
||||
// Re-build b as if it had been chained on top of a.
|
||||
using LHS_NP = CGAL::Named_function_parameters<T_a, Tag_a, Base_a>;
|
||||
using Combined = CGAL::Named_function_parameters<T_b, Tag_b, LHS_NP>;
|
||||
// Read b's value (Named_params_impl::v is the stored value).
|
||||
using Impl_b = CGAL::internal_np::Named_params_impl<T_b, Tag_b, CGAL::internal_np::No_property>;
|
||||
const auto& v_b = static_cast<const Impl_b&>(b).v;
|
||||
return Combined(v_b, a);
|
||||
}
|
||||
|
||||
} // namespace CGAL
|
||||
|
||||
#endif // CGAL_CONFORMAL_MAP_INTERNAL_PARAMETERS_H
|
||||
|
||||
@@ -322,3 +322,51 @@ TEST(CGALPhase8bLite, OutputUvMap_NormaliseLayout_TakesEffect)
|
||||
EXPECT_TRUE(std::isfinite(uv_norm[v].x()));
|
||||
}
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// 7. Named-parameter chaining via pipe-operator
|
||||
//
|
||||
// CGAL's `.a().b().c()` chaining requires modifying CGAL upstream, which
|
||||
// we don't do. conformallab++ provides a `|` operator that achieves the
|
||||
// same effect by left-to-right composition. These tests verify that the
|
||||
// chain is read back correctly by the entry functions.
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
TEST(CGALPhase8bLite, NamedParamPipe_MultipleParamsTakeEffect)
|
||||
{
|
||||
using K = CGAL::Simple_cartesian<double>;
|
||||
auto mesh = make_quad_strip();
|
||||
|
||||
auto uv = mesh.add_property_map<Vertex_index, K::Point_2>(
|
||||
"v:pipe_uv", K::Point_2(0, 0)).first;
|
||||
|
||||
// Chain three parameters using `|`.
|
||||
auto params = CGAL::parameters::gradient_tolerance(1e-12)
|
||||
| CGAL::parameters::max_iterations(500)
|
||||
| CGAL::parameters::output_uv_map(uv);
|
||||
|
||||
auto res = CGAL::discrete_conformal_map_euclidean(mesh, params);
|
||||
|
||||
EXPECT_TRUE(res.converged);
|
||||
EXPECT_LT(res.gradient_norm, 1e-10); // tight tolerance applied
|
||||
EXPECT_LE(res.iterations, 500);
|
||||
// UV pmap was populated.
|
||||
bool any_nonzero = false;
|
||||
for (auto v : mesh.vertices()) {
|
||||
if (std::abs(uv[v].x()) + std::abs(uv[v].y()) > 1e-10) {
|
||||
any_nonzero = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(any_nonzero);
|
||||
}
|
||||
|
||||
TEST(CGALPhase8bLite, NamedParamPipe_TwoParams)
|
||||
{
|
||||
// Pipe two parameters and verify both take effect.
|
||||
auto mesh = make_triangle();
|
||||
auto params = CGAL::parameters::max_iterations(0)
|
||||
| CGAL::parameters::gradient_tolerance(1e-6);
|
||||
auto res = CGAL::discrete_conformal_map_euclidean(mesh, params);
|
||||
EXPECT_EQ(res.iterations, 0); // max_iterations(0) blocks the loop
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user