[core] Expand = delete to catch inconvertible return types, add to_exp test

- TemplatableFn = delete now catches both stateful lambdas AND stateless
  lambdas with inconvertible return types (e.g., string -> int)
- Add test for to_exp with non-string output_type (lambda-wraps result)
This commit is contained in:
J. Nick Koston
2026-04-07 14:54:41 -10:00
parent 226908a64a
commit 0ec4e98374
2 changed files with 15 additions and 2 deletions
+6 -2
View File
@@ -54,8 +54,12 @@ template<typename T, typename... X> class TemplatableFn {
std::invocable<F, X...> &&std::convertible_to<std::invoke_result_t<F, X...>, T> &&std::is_empty_v<F>
&&std::default_initializable<F> : f_([](X... x) -> T { return static_cast<T>(F{}(x...)); }) {}
// Reject stateful lambdas (non-empty, i.e. capturing) with a clear error
template<typename F> TemplatableFn(F) requires std::invocable<F, X...> &&(!std::is_empty_v<F>) = delete;
// Reject any callable that didn't match the above (stateful lambdas or inconvertible return types)
template<typename F>
TemplatableFn(F) requires std::invocable<F, X...> &&
(!std::convertible_to<F, T (*)(X...)>) &&(!std::is_empty_v<F> ||
!std::convertible_to<std::invoke_result_t<F, X...>, T> ||
!std::default_initializable<F>) = delete;
bool has_value() const { return this->f_ != nullptr; }
+9
View File
@@ -684,6 +684,15 @@ async def test_templatable__with_to_exp_callable() -> None:
assert result == 84
@pytest.mark.asyncio
async def test_templatable__with_to_exp_callable_and_output_type() -> None:
"""When to_exp is provided with non-string output_type, result is lambda-wrapped."""
result = await cg.templatable(42, [], ct.int_, to_exp=lambda x: x * 2)
assert isinstance(result, cg.LambdaExpression)
assert result.capture == ""
@pytest.mark.asyncio
async def test_templatable__with_to_exp_dict() -> None:
"""When to_exp is a dict, value is looked up."""