Date: Mon, 25 Sep 2023 14:57:13 +0300
On 9/25/23 12:08, David Brown via Std-Proposals wrote:
> On 23/09/2023 20:35, Arthur O'Dwyer via Std-Proposals wrote:
>> On Sat, Sep 23, 2023 at 2:13 PM Chris Gary via Std-Proposals
>> <std-proposals_at_[hidden]
>> <mailto:std-proposals_at_[hidden]>> wrote:
>>
>>
>> *move and forward can be defined without compiler hooks. This is
>> where the language does not make architectural assumptions.*
>> *typeid() cannot.*
>>
>>
>> Right.
>>
>> In general: Architecture is disrupted when an unwanted or
>> incompatible role is introduced or required by a dependency. All C++
>> applications are now dependent on the standard library [...] It must
>> be possible to effectively use the language without the library.
>>
>>
>> I agree with you, as a general rule. But, as everyone's saying, you're
>> re-litigating a decision that was already made in
>> - C++20, with the type of `auto x = (1 <=> 2)` being
>> std::strong_ordering and the type of `auto x = (1. <=> 2)` being
>> std::partial_ordering;
>> and that decision was merely in line with the decisions C++ made in
>> - C++20, with basically everything in coroutines;
>> - C++17, with structured bindings' being dependent on std::tuple_size
>> and std::tuple_element <https://eel.is/c++draft/dcl.struct.bind#4>;
The part that depends on std::tuple_size and std::tuple_element is
specific to tuples. So if you are adapting tuples to structured
bindings, you are including <tuple> (otherwise, you wouldn't have
std::tuple available).
But you can use structured bindings without including <tuple>, if you
use them with types other than tuples. For arrays,
https://eel.is/c++draft/dcl.struct.bind#3 applies, and for structs
(including std::pair) - https://eel.is/c++draft/dcl.struct.bind#5.
https://eel.is/c++draft/dcl.struct.bind#4 doesn't apply because
std::tuple_size<E> does not name a complete class type, which is the
precondition for that paragraph.
>> - C++11, with ranged-for-loops' being dependent on
>> <https://eel.is/c++draft/stmt.ranged#1.3> the magic identifiers
>> "begin" and "end", and the type of `auto x = {1,2}` being
>> std::initializer_list<int>;
The ranged-for statement is specifically designed to be independent of
the standard library headers (as it either intrinsically detects size of
sized array types or invokes unqualified begin/end, which can be defined
anywhere, standard library or not). The fact that {1,2} is an
std::initializer_list is not part of ranged-for definition, it simply
fits the ranged-for definition so that it can be efficiently used with
it. Just as any other user-defined type that supports begin/end
overloads. Is that "fit" intentional? Sure. Does that make ranged-for
depend on std::initializer_list? Absolutely not.
>> - C++98, with the type of `auto x = typeid(int)` being std::type_info.
>>
>> You can't invoke a general principle to undo a specific
>> three-years-out-the-door decision, especially when that decision is in
>> line with two-decade-old decisions.
>>
>
> It is /way/ older than that - preceding C++ itself, and preceding
> standardised C. K&R C had the "sizeof" operator return the type
> "size_t", defined in <stdlib.h> and <stddef.h>. So having language
> features rely on definitions from standard library headers is a 50 year
> old tradition, not merely two decades old!
This one is backwards. sizeof returns an integral type that happens to
be typedefed as size_t in <cstddef>. Same as nullptr, which has some
(unspecified) type that is typedefed as nullptr_t in <cstddef>. You can
use both types without ever including <cstddef>.
Obviously, there are counterexamples, some of them listed above, where a
language feature has to depend on standard library. But I would agree
with the OP that it is preferable, if possible, to reduce such
dependencies. (I'm not agreeing that int as the result of a comparison
operator would be a good choice though.)
Including a standard library header has a compile time cost, especially
given that most standard library headers are too coarse grained.
Personally, I often find myself avoiding including <utility> just to get
std::move and std::forward and replace those with a static_cast in
public headers. Including <algorithm> just to get std::min/std::max also
feels unjustified in terms of cost vs. gained convenience. Defining
std::byte as a library type and a bunch of char*_t types as intrinsic
types also makes no sense to me.
And before you say "but modules!", no, modules are not there yet, and I
don't see them being widely used in a few coming years at the very
least, possibly decade. There is an enormous amount of code written with
plain old headers, and such code is still being written today because
compatibility.
> On 23/09/2023 20:35, Arthur O'Dwyer via Std-Proposals wrote:
>> On Sat, Sep 23, 2023 at 2:13 PM Chris Gary via Std-Proposals
>> <std-proposals_at_[hidden]
>> <mailto:std-proposals_at_[hidden]>> wrote:
>>
>>
>> *move and forward can be defined without compiler hooks. This is
>> where the language does not make architectural assumptions.*
>> *typeid() cannot.*
>>
>>
>> Right.
>>
>> In general: Architecture is disrupted when an unwanted or
>> incompatible role is introduced or required by a dependency. All C++
>> applications are now dependent on the standard library [...] It must
>> be possible to effectively use the language without the library.
>>
>>
>> I agree with you, as a general rule. But, as everyone's saying, you're
>> re-litigating a decision that was already made in
>> - C++20, with the type of `auto x = (1 <=> 2)` being
>> std::strong_ordering and the type of `auto x = (1. <=> 2)` being
>> std::partial_ordering;
>> and that decision was merely in line with the decisions C++ made in
>> - C++20, with basically everything in coroutines;
>> - C++17, with structured bindings' being dependent on std::tuple_size
>> and std::tuple_element <https://eel.is/c++draft/dcl.struct.bind#4>;
The part that depends on std::tuple_size and std::tuple_element is
specific to tuples. So if you are adapting tuples to structured
bindings, you are including <tuple> (otherwise, you wouldn't have
std::tuple available).
But you can use structured bindings without including <tuple>, if you
use them with types other than tuples. For arrays,
https://eel.is/c++draft/dcl.struct.bind#3 applies, and for structs
(including std::pair) - https://eel.is/c++draft/dcl.struct.bind#5.
https://eel.is/c++draft/dcl.struct.bind#4 doesn't apply because
std::tuple_size<E> does not name a complete class type, which is the
precondition for that paragraph.
>> - C++11, with ranged-for-loops' being dependent on
>> <https://eel.is/c++draft/stmt.ranged#1.3> the magic identifiers
>> "begin" and "end", and the type of `auto x = {1,2}` being
>> std::initializer_list<int>;
The ranged-for statement is specifically designed to be independent of
the standard library headers (as it either intrinsically detects size of
sized array types or invokes unqualified begin/end, which can be defined
anywhere, standard library or not). The fact that {1,2} is an
std::initializer_list is not part of ranged-for definition, it simply
fits the ranged-for definition so that it can be efficiently used with
it. Just as any other user-defined type that supports begin/end
overloads. Is that "fit" intentional? Sure. Does that make ranged-for
depend on std::initializer_list? Absolutely not.
>> - C++98, with the type of `auto x = typeid(int)` being std::type_info.
>>
>> You can't invoke a general principle to undo a specific
>> three-years-out-the-door decision, especially when that decision is in
>> line with two-decade-old decisions.
>>
>
> It is /way/ older than that - preceding C++ itself, and preceding
> standardised C. K&R C had the "sizeof" operator return the type
> "size_t", defined in <stdlib.h> and <stddef.h>. So having language
> features rely on definitions from standard library headers is a 50 year
> old tradition, not merely two decades old!
This one is backwards. sizeof returns an integral type that happens to
be typedefed as size_t in <cstddef>. Same as nullptr, which has some
(unspecified) type that is typedefed as nullptr_t in <cstddef>. You can
use both types without ever including <cstddef>.
Obviously, there are counterexamples, some of them listed above, where a
language feature has to depend on standard library. But I would agree
with the OP that it is preferable, if possible, to reduce such
dependencies. (I'm not agreeing that int as the result of a comparison
operator would be a good choice though.)
Including a standard library header has a compile time cost, especially
given that most standard library headers are too coarse grained.
Personally, I often find myself avoiding including <utility> just to get
std::move and std::forward and replace those with a static_cast in
public headers. Including <algorithm> just to get std::min/std::max also
feels unjustified in terms of cost vs. gained convenience. Defining
std::byte as a library type and a bunch of char*_t types as intrinsic
types also makes no sense to me.
And before you say "but modules!", no, modules are not there yet, and I
don't see them being widely used in a few coming years at the very
least, possibly decade. There is an enormous amount of code written with
plain old headers, and such code is still being written today because
compatibility.
Received on 2023-09-25 11:57:22