Skip to content
This repository was archived by the owner on Mar 21, 2024. It is now read-only.
This repository was archived by the owner on Mar 21, 2024. It is now read-only.

transform_output_iterator (and transform_input_output_iterator) unusable with reduce_by_key due to missing default constructor #1804

@harrism

Description

@harrism

I have been unable to use a thrust::transform_output_iterator as the output iterator for thrust::reduce_by_key. After much head scratching and digging, I found the cause:

Unlike all other Thrust iterators, transform_output_iterator and transform_input_output_iterator have no default constructor. And reduce_by_key constructs an empty pair or iterators for its result:

pair<KeysOutputIt, ValuesOutputIt> result{};

If one of the types in the pair is a transform_output_iterator, compilation will fail with: error: no instance of constructor "thrust::transform_output_iterator<UnaryFunction, OutputIterator>::transform_output_iterator [with UnaryFunction = ...]" matches the argument list.

Here's a repro: https://godbolt.org/z/P98YYh1x1

We have found two ways to fix this. The simple way is to add the default constructor. Here's a demo of that. This has a namespace thrust_fix which has its own transform_output_iterator. https://godbolt.org/z/rK3Ec73bz


Another way is to change the scary self-referential macro THRUST_INDEX_TYPE_DISPATCH so that it doesn't require the "Initialize Then Modify" antipattern. Its approach of assigning to the result requires pre-declaring that result, which means auto can't be used.

#define THRUST_INDEX_TYPE_DISPATCH(status, call, count, arguments) \
if (count <= thrust::detail::integer_traits<thrust::detail::int32_t>::const_max) { \
auto THRUST_PP_CAT2(count, _fixed) = static_cast<thrust::detail::int32_t>(count); \
status = call arguments; \
} \
else { \
auto THRUST_PP_CAT2(count, _fixed) = static_cast<thrust::detail::int64_t>(count); \
status = call arguments; \
}

(Not to mention that requiring the caller to inject the renamed count_fixed variable into the arguments list is uuuugly.)

This could be done differently using an IILE:

#define THRUST_INDEX_TYPE_DISPATCH_NEW(call, count, arguments)                          \
  [&]() {                                                                               \
    if (count > thrust::detail::integer_traits<thrust::detail::int32_t>::const_max) {  \
      auto THRUST_PP_CAT2(count, _fixed) = static_cast<thrust::detail::int32_t>(count); \
      return call arguments;                                                          \
    } else {                                                                            \
      auto THRUST_PP_CAT2(count, _fixed) = static_cast<thrust::detail::int64_t>(count); \
      return call arguments;
    }                                                                                   \
  }();

This means we no longer have to pre-declare a pair of iterators in reduce_by_key. We just return directly:

return THRUST_INDEX_TYPE_DISPATCH(reduce_by_key_dispatch,
                                    num_items,
                                    (policy,
                                     keys_first,
                                     num_items_fixed,
                                     values_first,
                                     keys_output,
                                     values_output,
                                     equality_op,
                                     reduction_op));

There may be limitations of the lambda capture by reference used here that I haven't thought about, and this would impact a lot of Thrust code. So I recommend just adding the default constructors. :)

CC @isVoid

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions