Date: Fri, 21 Nov 2025 11:42:28 -0500
On Tue, Nov 11, 2025 at 7:53 PM 叶易安 via Std-Proposals <
std-proposals_at_[hidden]> wrote:
> On Mon, Nov 10, 2025 at 1:46?PM Arthur O'Dwyer via Std-Proposals
>> <std-proposals_at_[hidden]> wrote:
>> >
>> > On Mon, Nov 10, 2025 at 1:06?PM Jonathan Wakely <cxx_at_[hidden]> wrote:
>> >>
>> >> On Mon, 10 Nov 2025, 15:51 Arthur O'Dwyer via Std-Proposals, <
>> std-proposals_at_[hidden]> wrote:
>> >>>
>> >>> On Mon, Nov 10, 2025 at 1:15?AM ??? via Std-Proposals <
>> std-proposals_at_[hidden]> wrote:
>> >>>>
>> >>>> We notice that we have 2 available 'tags' in C++ standard library
>> now:
>> >>>>
>> >>>> =====
>> >>>> class my_iterator { using iterator_concept =
>> std::random_access_iterator_tag };
>> >>>> class my_sender { using sender_concept = std::execution::sender_t; }
>> >>>> =====
>> >>>>
>> >>>> Oops, the first ends with `_tag` and the second ends with `_t`. But
>> they share the same effect (to make a class satisfy a particular concept).
>> Should we unify them?
>> >>>> Hmmm, before that let's see where we use `_tag` and `_t` in C++
>> standard library.
>> >>>>
>> >>>> xxx_t:
>> >>>> 1. <type_traits>. When we have `add_const<T>::type`, we typedef an
>> `add_const_t<T>`.
>> >>>> 2. Typedef which unifies different platforms. `uint8_t`, `wchar_t`,
>> `clock_t`, etc.
>> >>>> 3. Utility type of global constexpr variable. e.g. nullopt_t,
>> unexpected_t, etc.
>> >>>> 4. Utility type of function overload parameters. e.g.
>> unique_lock(defer_lock_t), vector(from_range_t), etc.
>> >>>>
>> >>>> xxx_tag:
>> >>>> 1. Customize xxx_concept in class. e.g. using iterator_concept
>> forward/bidirectional/.../random_access_iterator_tag.
>> >>>>
>> >>>> Should we rename `std::execution::sender_t` into
>> `std::execution::sender_tag`?
>> >>>> Thank you!
>> >>>
>> >>>
>> >>> Yes, it certainly seems so (at first glance) to me. Like you, I'm not
>> aware of any prior art for using `_t` as the suffix for a tag type.
>> (Although >>> C++20 Coroutines used no suffix at all for theirs, e.g.
>> `std::suspend_always`.)
>> >>> The tag types `sender_t` and `receiver_t` were introduced in P2300
>> <https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p2300r10.html>.
>> >>> I'm cc'ing a couple of the P2300 authors for their thoughts, but
>> yeah, unless someone presents a very compelling reason why they're _t
>> instead of _tag, I think a new LWG issue should be filed.
>>
>> >>> Thank you for bringing this up!
>>
> >>> -Authur
>
> >>
>> >>
>> >> Aren't allocator_arg_t, inplace_t, defer_lock_t etc. tag types?
>> >
>> >
>> > Not in the same sense. Those are the kind of tag type where we write
>> > namespace std { struct foo_t {}; constexpr foo_t foo; }
>> > usercode(std::foo, ...);
>> >
>> > sender_t and receiver_t and foo_iterator_tag are the kind of tag type
>> where we write
>> > namespace std { struct foo_tag {}; }
>> > struct Usercode { using foo_concept = std::foo_tag; };
>> >
>> > We do indeed have the same kind of overloaded-English-word problem with
>> "tag type" that we have with "traits class," but it's true that sender_t
>> and receiver_t are the same kind as foo_iterator_tag and not at all the
>> same kind as allocator_arg_t, inplace_t, inplace_type_t, defer_lock_t,
>> adopt_lock_t, etc. Nor are they the same kind as std::nullopt_t, nor
>> std::monostate.
>> >
>> >> Using a "tag" suffix is unique to the iterator categories, which are
>> from 30 years ago and that design hasn't been repeated elsewhere in the
>> library.
>> >
>> >
>> > But that's precisely because no other place in the library has repeated
>> the
>> > using foo_category = ...;
>> > using foo_concept = ...;
>> > design, before now, isn't it? AFAIR the closest we've ever come between
>> C++98's iterator concept tags and C++26 S/R concept tags is the (IMO
>> ill-advised)
>> > using is_transparent = void;
>> > which didn't introduce any new tag types.
>>
>> Well, there's already been quite some drift as to what `_t` means.
>>
>> Initially, derived from C, `_t` is used for some kind of
>> implementation-defined type that is exposed via a typedef. `int32_t`
>> is a typedef for some implementation-specific type. And C++ inherited
>> this meaning, applying it to `std::nullptr_t` and the like.
>>
>> The concept tags always used `_tag`, since they're not like the above.
>> First and foremost, they're class types.
>>
>> The problem is that someone decided that certain interface tags used
>> for overload resolution should use the `_t` suffix. They *should* have
>> used `_tag`, but they didn't.
>>
>> So now we have this distinction between "tag" as "class type used in
>> by concepts to detect a property of a type that is otherwise
>> undetectable by a concept," and "tag" as "class type used to
>> disambiguate overload resolution". The issue is that I don't think
>> this distinction really matters to anybody. Yes, these are two
>> different uses of the "tag" concept, but I don't think someone really
>> needs to be able to look at a tag type's name and know whether it's
>> used for overload resolution or concept checking. You'll find out by
>> how it's used in code.
>>
>> Unfortunately, that ship has already sailed, so we may as well
>> preserve the otherwise useless distinction.
>>
>
> Thank you. Since '_tag' types do serve a different purpose from '_t'
> types, and `std::execution` *should* have used xxx_tag (but unfortunately
> the author just chose the xxx_t when designing this library), I think a
> new LWG issue should be filed too.
>
>
> Moreover, the distinction matters at some interesting places. Not only C++
> std library uses this naming rule, but also other libraries who learn from
> C++ std library share the same rule that '_tag for in-class
> concept/category typedef, _t for others`. Lets see Boost as an example.
>
> =====
>
> Boost.Graph has directed_tag/undirected_tag/bidirectional_tag,
> adjacent_graph_tag/vertix_list_graph_tag/edge_list_tag/,
> allow_edge_parallel_tag/disallow_edge_parallel_tag, etc.
>
> Boost.Fusion has
> deque_tag/set_tag/std_array_tag/boost_array_tag/mpl_sequence_tag, etc.
>
> Boost.Numeric.Odeint has controlled_stepper_tag/dense_output_stepper_tag,
> etc.
>
> Boost.Numeric.Ublas has row_major_tag/column_major_tag,
> packed_tag/dense_tag/sparse_tag/dense_proxy_tag/spase_proxy_tag, etc.
>
> =====
>
> Might `std::execution::sender/receiver/scheduler_concept` stay consistent
> with `iterator_concept`, which would also help clarify the naming rules for
> std-style third-party libraries? I hope so.
>
>
> Thank you! :)
>
Update: On November 12 I sent an issue to LWG. They sent it back, saying
"this is LEWG territory."
On November 15 I sent an email to lib-ext@, the LEWG reflector; and I
cc'ed 叶易安; and I cc'ed the nine authors of P2300. As of November 21, I
haven't received any replies to that email.
I'm planning to write up this distinction in a blog post, and then turn
that blog post into a paper, if 叶易安 is willing to coauthor. I don't believe
it will get anywhere if I'm the only person working on it.
–Arthur
std-proposals_at_[hidden]> wrote:
> On Mon, Nov 10, 2025 at 1:46?PM Arthur O'Dwyer via Std-Proposals
>> <std-proposals_at_[hidden]> wrote:
>> >
>> > On Mon, Nov 10, 2025 at 1:06?PM Jonathan Wakely <cxx_at_[hidden]> wrote:
>> >>
>> >> On Mon, 10 Nov 2025, 15:51 Arthur O'Dwyer via Std-Proposals, <
>> std-proposals_at_[hidden]> wrote:
>> >>>
>> >>> On Mon, Nov 10, 2025 at 1:15?AM ??? via Std-Proposals <
>> std-proposals_at_[hidden]> wrote:
>> >>>>
>> >>>> We notice that we have 2 available 'tags' in C++ standard library
>> now:
>> >>>>
>> >>>> =====
>> >>>> class my_iterator { using iterator_concept =
>> std::random_access_iterator_tag };
>> >>>> class my_sender { using sender_concept = std::execution::sender_t; }
>> >>>> =====
>> >>>>
>> >>>> Oops, the first ends with `_tag` and the second ends with `_t`. But
>> they share the same effect (to make a class satisfy a particular concept).
>> Should we unify them?
>> >>>> Hmmm, before that let's see where we use `_tag` and `_t` in C++
>> standard library.
>> >>>>
>> >>>> xxx_t:
>> >>>> 1. <type_traits>. When we have `add_const<T>::type`, we typedef an
>> `add_const_t<T>`.
>> >>>> 2. Typedef which unifies different platforms. `uint8_t`, `wchar_t`,
>> `clock_t`, etc.
>> >>>> 3. Utility type of global constexpr variable. e.g. nullopt_t,
>> unexpected_t, etc.
>> >>>> 4. Utility type of function overload parameters. e.g.
>> unique_lock(defer_lock_t), vector(from_range_t), etc.
>> >>>>
>> >>>> xxx_tag:
>> >>>> 1. Customize xxx_concept in class. e.g. using iterator_concept
>> forward/bidirectional/.../random_access_iterator_tag.
>> >>>>
>> >>>> Should we rename `std::execution::sender_t` into
>> `std::execution::sender_tag`?
>> >>>> Thank you!
>> >>>
>> >>>
>> >>> Yes, it certainly seems so (at first glance) to me. Like you, I'm not
>> aware of any prior art for using `_t` as the suffix for a tag type.
>> (Although >>> C++20 Coroutines used no suffix at all for theirs, e.g.
>> `std::suspend_always`.)
>> >>> The tag types `sender_t` and `receiver_t` were introduced in P2300
>> <https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p2300r10.html>.
>> >>> I'm cc'ing a couple of the P2300 authors for their thoughts, but
>> yeah, unless someone presents a very compelling reason why they're _t
>> instead of _tag, I think a new LWG issue should be filed.
>>
>> >>> Thank you for bringing this up!
>>
> >>> -Authur
>
> >>
>> >>
>> >> Aren't allocator_arg_t, inplace_t, defer_lock_t etc. tag types?
>> >
>> >
>> > Not in the same sense. Those are the kind of tag type where we write
>> > namespace std { struct foo_t {}; constexpr foo_t foo; }
>> > usercode(std::foo, ...);
>> >
>> > sender_t and receiver_t and foo_iterator_tag are the kind of tag type
>> where we write
>> > namespace std { struct foo_tag {}; }
>> > struct Usercode { using foo_concept = std::foo_tag; };
>> >
>> > We do indeed have the same kind of overloaded-English-word problem with
>> "tag type" that we have with "traits class," but it's true that sender_t
>> and receiver_t are the same kind as foo_iterator_tag and not at all the
>> same kind as allocator_arg_t, inplace_t, inplace_type_t, defer_lock_t,
>> adopt_lock_t, etc. Nor are they the same kind as std::nullopt_t, nor
>> std::monostate.
>> >
>> >> Using a "tag" suffix is unique to the iterator categories, which are
>> from 30 years ago and that design hasn't been repeated elsewhere in the
>> library.
>> >
>> >
>> > But that's precisely because no other place in the library has repeated
>> the
>> > using foo_category = ...;
>> > using foo_concept = ...;
>> > design, before now, isn't it? AFAIR the closest we've ever come between
>> C++98's iterator concept tags and C++26 S/R concept tags is the (IMO
>> ill-advised)
>> > using is_transparent = void;
>> > which didn't introduce any new tag types.
>>
>> Well, there's already been quite some drift as to what `_t` means.
>>
>> Initially, derived from C, `_t` is used for some kind of
>> implementation-defined type that is exposed via a typedef. `int32_t`
>> is a typedef for some implementation-specific type. And C++ inherited
>> this meaning, applying it to `std::nullptr_t` and the like.
>>
>> The concept tags always used `_tag`, since they're not like the above.
>> First and foremost, they're class types.
>>
>> The problem is that someone decided that certain interface tags used
>> for overload resolution should use the `_t` suffix. They *should* have
>> used `_tag`, but they didn't.
>>
>> So now we have this distinction between "tag" as "class type used in
>> by concepts to detect a property of a type that is otherwise
>> undetectable by a concept," and "tag" as "class type used to
>> disambiguate overload resolution". The issue is that I don't think
>> this distinction really matters to anybody. Yes, these are two
>> different uses of the "tag" concept, but I don't think someone really
>> needs to be able to look at a tag type's name and know whether it's
>> used for overload resolution or concept checking. You'll find out by
>> how it's used in code.
>>
>> Unfortunately, that ship has already sailed, so we may as well
>> preserve the otherwise useless distinction.
>>
>
> Thank you. Since '_tag' types do serve a different purpose from '_t'
> types, and `std::execution` *should* have used xxx_tag (but unfortunately
> the author just chose the xxx_t when designing this library), I think a
> new LWG issue should be filed too.
>
>
> Moreover, the distinction matters at some interesting places. Not only C++
> std library uses this naming rule, but also other libraries who learn from
> C++ std library share the same rule that '_tag for in-class
> concept/category typedef, _t for others`. Lets see Boost as an example.
>
> =====
>
> Boost.Graph has directed_tag/undirected_tag/bidirectional_tag,
> adjacent_graph_tag/vertix_list_graph_tag/edge_list_tag/,
> allow_edge_parallel_tag/disallow_edge_parallel_tag, etc.
>
> Boost.Fusion has
> deque_tag/set_tag/std_array_tag/boost_array_tag/mpl_sequence_tag, etc.
>
> Boost.Numeric.Odeint has controlled_stepper_tag/dense_output_stepper_tag,
> etc.
>
> Boost.Numeric.Ublas has row_major_tag/column_major_tag,
> packed_tag/dense_tag/sparse_tag/dense_proxy_tag/spase_proxy_tag, etc.
>
> =====
>
> Might `std::execution::sender/receiver/scheduler_concept` stay consistent
> with `iterator_concept`, which would also help clarify the naming rules for
> std-style third-party libraries? I hope so.
>
>
> Thank you! :)
>
Update: On November 12 I sent an issue to LWG. They sent it back, saying
"this is LEWG territory."
On November 15 I sent an email to lib-ext@, the LEWG reflector; and I
cc'ed 叶易安; and I cc'ed the nine authors of P2300. As of November 21, I
haven't received any replies to that email.
I'm planning to write up this distinction in a blog post, and then turn
that blog post into a paper, if 叶易安 is willing to coauthor. I don't believe
it will get anywhere if I'm the only person working on it.
–Arthur
Received on 2025-11-21 16:42:43
