On Mon, Nov 10, 2025 at 1:46?PM Arthur O'Dwyer via Std-Proposals
<std-proposals@lists.isocpp.org> wrote:
>
> On Mon, Nov 10, 2025 at 1:06?PM Jonathan Wakely <cxx@kayari.org> wrote:
>>
>> On Mon, 10 Nov 2025, 15:51 Arthur O'Dwyer via Std-Proposals, <std-proposals@lists.isocpp.org> wrote:
>>>
>>> On Mon, Nov 10, 2025 at 1:15?AM ??? via Std-Proposals <std-proposals@lists.isocpp.org> 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.>>> 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.
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! :)