C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Std-Proposals Digest, Vol 80, Issue 9

From: 叶易安 <shyeyian_at_[hidden]>
Date: Wed, 12 Nov 2025 08:53:13 +0800
> 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! :)

Received on 2025-11-12 00:53:29