Skip to content

Commit 83dfbdc

Browse files
committed
Make query specification order-independent
Change the declarations of `function<...>` to inherit from canonical specializations of `_func_impl` so that the queries are always sorted and uniqued.
1 parent 2554f62 commit 83dfbdc

3 files changed

Lines changed: 149 additions & 18 deletions

File tree

include/exec/any_sender_of.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ namespace experimental::execution
5656

5757
template <class... Sigs>
5858
requires(_qry_detail::is_query_function_v<Sigs> && ...)
59-
struct queries;
59+
struct queries
60+
{};
6061

6162
template <class Sigs, class Queries = queries<>>
6263
struct any_receiver;

include/exec/function.hpp

Lines changed: 60 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,13 @@
1717

1818
#include "../stdexec/__detail/__completion_signatures.hpp"
1919
#include "../stdexec/__detail/__concepts.hpp"
20+
#include "../stdexec/__detail/__meta.hpp"
2021
#include "../stdexec/__detail/__read_env.hpp"
2122
#include "../stdexec/__detail/__receivers.hpp"
2223
#include "../stdexec/__detail/__sender_concepts.hpp"
24+
#include "../stdexec/__detail/__static_vector.hpp"
2325
#include "../stdexec/__detail/__tuple.hpp"
26+
#include "../stdexec/__detail/__typeinfo.hpp"
2427
#include "../stdexec/__detail/__utility.hpp"
2528
#include "../stdexec/functional.hpp"
2629

@@ -287,10 +290,10 @@ namespace experimental::execution
287290
};
288291

289292
template <class Sigs>
290-
struct _canonical_t;
293+
struct _canonical_fn;
291294

292295
template <class... Sigs>
293-
struct _canonical_t<completion_signatures<Sigs...>>
296+
struct _canonical_fn<completion_signatures<Sigs...>>
294297
{
295298
consteval auto operator()() const noexcept
296299
{
@@ -303,18 +306,59 @@ namespace experimental::execution
303306
}
304307
};
305308

309+
template <class... Queries>
310+
struct _canonical_fn<queries<Queries...>>
311+
{
312+
private:
313+
// sort and unique the function types in Queries... into an array of __mtypeids
314+
static consteval auto get_sigs() noexcept
315+
{
316+
using sig_array_t = __static_vector<__type_index, sizeof...(Queries)>;
317+
auto sigs = sig_array_t{__mtypeid<Queries>...};
318+
319+
std::ranges::sort(sigs);
320+
321+
auto const end = std::ranges::unique(sigs).begin();
322+
sigs.erase(end, sigs.end());
323+
324+
return sigs;
325+
}
326+
327+
public:
328+
consteval auto operator()() const noexcept
329+
{
330+
constexpr auto sigs = get_sigs();
331+
332+
constexpr auto fn = [=]<std::size_t... Is>(__indices<Is...>)
333+
{
334+
return queries<__msplice<sigs[Is]>...>();
335+
};
336+
337+
return fn(__make_indices<sigs.size()>());
338+
}
339+
};
340+
341+
template <>
342+
struct _canonical_fn<queries<>>
343+
{
344+
consteval auto operator()() const noexcept
345+
{
346+
return queries<>();
347+
}
348+
};
349+
306350
template <class Sigs>
307-
inline constexpr _canonical_t<Sigs> _canonical{};
351+
inline constexpr _canonical_fn<Sigs> _canonical{};
308352

309353
template <class Sigs>
310-
using _canonical_sigs_t = decltype(_canonical<Sigs>());
354+
using _canonical_t = decltype(_canonical<Sigs>());
311355

312356
// Given a return type and a bool indicating whether the function is noexcept,
313357
// compute the appropriate completion_signatures. The result is a set_value
314358
// overload taking either Return&& or no args when Return is void, set_stopped,
315359
// and, when the function type is not noexcept, set_error(std::exception_ptr)
316360
template <class Return, bool NoExcept>
317-
using _sigs_from_t = _canonical_sigs_t<STDEXEC::__concat_completion_signatures_t<
361+
using _sigs_from_t = _canonical_t<STDEXEC::__concat_completion_signatures_t<
318362
STDEXEC::completion_signatures<STDEXEC::__single_value_sig_t<Return>,
319363
STDEXEC::set_stopped_t()>,
320364
STDEXEC::__eptr_completion_unless_t<STDEXEC::__mbool<NoExcept>>>>;
@@ -416,10 +460,10 @@ namespace experimental::execution
416460
template <class... Args, class Sigs>
417461
requires STDEXEC::__is_instance_of<Sigs, STDEXEC::completion_signatures>
418462
struct function<STDEXEC::sender_tag(Args...), Sigs>
419-
: _func::_func_impl<STDEXEC::sender_tag(Args...), _func::_canonical_sigs_t<Sigs>, queries<>>
463+
: _func::_func_impl<STDEXEC::sender_tag(Args...), _func::_canonical_t<Sigs>, queries<>>
420464
{
421465
using base =
422-
_func::_func_impl<STDEXEC::sender_tag(Args...), _func::_canonical_sigs_t<Sigs>, queries<>>;
466+
_func::_func_impl<STDEXEC::sender_tag(Args...), _func::_canonical_t<Sigs>, queries<>>;
423467

424468
using base::base;
425469

@@ -449,11 +493,11 @@ namespace experimental::execution
449493
struct function<Return(Args...), queries<Queries...>>
450494
: _func::_func_impl<STDEXEC::sender_tag(Args...),
451495
_func::_sigs_from_t<Return, false>,
452-
queries<Queries...>>
496+
_func::_canonical_t<queries<Queries...>>>
453497
{
454498
using base = _func::_func_impl<STDEXEC::sender_tag(Args...),
455499
_func::_sigs_from_t<Return, false>,
456-
queries<Queries...>>;
500+
_func::_canonical_t<queries<Queries...>>>;
457501

458502
using base::base;
459503

@@ -483,11 +527,11 @@ namespace experimental::execution
483527
struct function<Return(Args...) noexcept, queries<Queries...>>
484528
: _func::_func_impl<STDEXEC::sender_tag(Args...),
485529
_func::_sigs_from_t<Return, true>,
486-
queries<Queries...>>
530+
_func::_canonical_t<queries<Queries...>>>
487531
{
488532
using base = _func::_func_impl<STDEXEC::sender_tag(Args...),
489533
_func::_sigs_from_t<Return, true>,
490-
queries<Queries...>>;
534+
_func::_canonical_t<queries<Queries...>>>;
491535

492536
using base::base;
493537

@@ -518,13 +562,12 @@ namespace experimental::execution
518562
STDEXEC::completion_signatures<Sigs...>,
519563
queries<Queries...>>
520564
: _func::_func_impl<STDEXEC::sender_tag(Args...),
521-
_func::_canonical_sigs_t<STDEXEC::completion_signatures<Sigs...>>,
522-
queries<Queries...>>
565+
_func::_canonical_t<STDEXEC::completion_signatures<Sigs...>>,
566+
_func::_canonical_t<queries<Queries...>>>
523567
{
524-
using base =
525-
_func::_func_impl<STDEXEC::sender_tag(Args...),
526-
_func::_canonical_sigs_t<STDEXEC::completion_signatures<Sigs...>>,
527-
queries<Queries...>>;
568+
using base = _func::_func_impl<STDEXEC::sender_tag(Args...),
569+
_func::_canonical_t<STDEXEC::completion_signatures<Sigs...>>,
570+
_func::_canonical_t<queries<Queries...>>>;
528571

529572
using base::base;
530573

test/exec/test_function.cpp

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,4 +391,91 @@ namespace
391391
REQUIRE(f4 == f5);
392392
}
393393
}
394+
395+
TEST_CASE("queries specification is order-independent", "[types][function]")
396+
{
397+
constexpr auto query1 = [](auto const &) noexcept
398+
{
399+
return 0;
400+
};
401+
402+
constexpr auto query2 = [](auto const &, int i)
403+
{
404+
return (double) i;
405+
};
406+
407+
using query1_t = decltype(query1);
408+
using query2_t = decltype(query2);
409+
410+
using func1_t =
411+
exec::function<int(int), exec::queries<int(query1_t) noexcept, double(query2_t, int)>>;
412+
413+
using func2_t =
414+
exec::function<int(int), exec::queries<double(query2_t, int), int(query1_t) noexcept>>;
415+
416+
SECTION("the function types are not the same as each other...")
417+
{
418+
STATIC_REQUIRE(!std::same_as<func1_t, func2_t>);
419+
}
420+
421+
SECTION("...but they both inherit from the same _func_impl base")
422+
{
423+
STATIC_REQUIRE(std::same_as<func1_t::base, func2_t::base>);
424+
}
425+
426+
SECTION("move construction works in all directions with both types")
427+
{
428+
STATIC_REQUIRE(std::constructible_from<func1_t, func1_t>);
429+
STATIC_REQUIRE(std::constructible_from<func1_t, func2_t>);
430+
STATIC_REQUIRE(std::constructible_from<func2_t, func1_t>);
431+
STATIC_REQUIRE(std::constructible_from<func2_t, func2_t>);
432+
}
433+
434+
SECTION("copy construction works in all directions with both types")
435+
{
436+
STATIC_REQUIRE(std::constructible_from<func1_t, func1_t const &>);
437+
STATIC_REQUIRE(std::constructible_from<func1_t, func2_t const &>);
438+
STATIC_REQUIRE(std::constructible_from<func2_t, func1_t const &>);
439+
STATIC_REQUIRE(std::constructible_from<func2_t, func2_t const &>);
440+
}
441+
442+
SECTION("move-assignment works in every direction with both types")
443+
{
444+
STATIC_REQUIRE(std::assignable_from<func1_t &, func1_t>);
445+
STATIC_REQUIRE(std::assignable_from<func1_t &, func2_t>);
446+
STATIC_REQUIRE(std::assignable_from<func2_t &, func1_t>);
447+
STATIC_REQUIRE(std::assignable_from<func2_t &, func2_t>);
448+
}
449+
450+
SECTION("copy-assignment works in every direction with both types")
451+
{
452+
STATIC_REQUIRE(std::assignable_from<func1_t &, func1_t const &>);
453+
STATIC_REQUIRE(std::assignable_from<func1_t &, func2_t const &>);
454+
STATIC_REQUIRE(std::assignable_from<func2_t &, func1_t const &>);
455+
STATIC_REQUIRE(std::assignable_from<func2_t &, func2_t const &>);
456+
}
457+
458+
SECTION("instances are mutually comparable with ==")
459+
{
460+
// identical copies in slightly different types
461+
func1_t f1(42, ex::just);
462+
func2_t f2(f1);
463+
464+
// differing curried arguments from above
465+
func1_t f3(45, ex::just);
466+
func2_t f4(45, ex::just);
467+
468+
REQUIRE(f1 == f1);
469+
REQUIRE(f1 == f2);
470+
REQUIRE(f2 == f1);
471+
REQUIRE(f2 == f2);
472+
473+
REQUIRE(f1 != f3);
474+
REQUIRE(f3 != f1);
475+
REQUIRE(f2 != f3);
476+
REQUIRE(f3 != f2);
477+
478+
REQUIRE(f3 == f4);
479+
}
480+
}
394481
} // namespace

0 commit comments

Comments
 (0)