brevzin / cpp_proposals Goto Github PK
View Code? Open in Web Editor NEWMy WG21 proposals
My WG21 proposals
Hello Barry,
I've recently implemented in C++20 a custom range adaptor cxx::chunk_evenly()
:
While doing so, I've realized that, in order to have the ability to use the pipe operator,
I need to implement the std::ranges::range_adaptor_closure
class template from P2387R3:
After getting myself familiar with P2387R3, I want to say that,
I am strongly in favor of it and glad that it was accepted into the C++23 draft.
Finally all C++ developers will have the ability to implement custom range adaptors,
which will be composable with standard range adaptors!
However, even though the cxx::ranges::range_adaptor_closure_interface
class template
does not support composing, via the pipe operator, custom range adaptor closures
with standard range adaptor closures, and therefore, when creating a pipeline,
the cxx::chunk_evenly()
range adaptor is not composable with standard range adaptors:
[Compiler Explorer] - create a pipeline
const auto pipeline = cxx::chunk_evenly(3) | std::views::take(2); // ^ // |___ does not compile!
it still has a couple of advantages, compared to the std::ranges::range_adaptor_closure
,
which I think are worth adopting:
more suitable name
The std::ranges::range_adaptor_closure
is not a range adaptor closure itself.
It is a class template, which range adaptor closures (or rather types,
which aim to model the range_adaptor_closure
concept) should inherit from CRTP-style.
Since that class template provides essential part of the interface,
that all range adaptor closures are characterized by,
that is invocability and composability via the pipe operator,
it should be renamed to std::ranges::range_adaptor_closure_interface
.
Please notice that, the proposed new name will be consistent with
the std::ranges::view_interface
class template,
which has a similar role in the context of the std::ranges::view
concept.
Moreover the name std::ranges::range_adaptor_closure
should be
reserved for the range_adaptor_closure
concept,
which represents the set of types of all range adaptor closure objects.
constrained pipe operators
The definition of the std::ranges::range_adaptor_closure
concept
is actually already specified in 26.7.2 [range.adaptor.object].
Unfortunately defining the std::ranges::range_adaptor_closure
concept in practice
is incredibly difficult, since it requires defining an archetype for
the std::ranges::viewable_range
concept, which I just was not able to do.
Side Note:
Since, a range adaptor object is a customization point object
that accepts aviewable_range
as its first argument and returns aview
,
a range adaptor closure object should be a unary function object
that accepts aviewable_range
as its only argument.
Fortunately I was able to define two (exposition-only) concepts:
cxx::ranges::maybe_range_adaptor_closure
cxx::ranges::range_adaptor_for<viewable_range>
and at least partially constrain the pipe operators using them.
Ideally the std::ranges::range_adaptor_closure
concept would be added to
the standard library, since it would be useful for defining customizable pipelines:
auto create_pipeline (std::ranges::range_adaptor_closure auto&& op)
{
return std::views::take(2) | op | std::views::reverse;
}
However, this can be done in a future C++ standard and
requiring the std::ranges::range_adaptor_closure_interface
class template
to provide at least partially constrained pipe operators is better than nothing.
I hope this feedback is valuable and
it is not tool late to at least rename the std::ranges::range_adaptor_closure
to std::ranges::range_adaptor_closure_interface
before C++23 will approved and released.
Thank you, Mateusz Zych
I’m getting around this problem so far.
template <typename T, size_t N>
struct array_size {
constexpr array_size(T (&)[N]) { }
constexpr array_size(T(&&)[N]) { }
constexpr operator size_t() { return N; }
};
template <typename T, size_t N>
array_size(T (&)[N]) -> array_size<T, N>;
template <typename T, size_t N>
array_size(T(&&)[N]) -> array_size<T, N>;
void check(int const (¶m)[3])
{
int local[] { 1, 2, 3 };
[[maybe_unused]] constexpr size_t s0 = array_size(local); // ok
[[maybe_unused]] constexpr size_t s1 = array_size(decltype(param) {}); // ok
}
Working code from my project.
template <class T>
bool writeMultipleCoils(uint16_t regAddress, T& coils) requires std::is_array_v<T>
{
...
constexpr size_t coilsCount = array_size(T {});
constexpr uint8_t dataSize = coilsCount / 8 + (coilsCount % 8 ? 1 : 0);
uint8_t data[dataSize] {};
...
}
I'm very impressed with the huge contribution you make to C++.
I want to offer you an idea for improving C++.
Often during developing, it is necessary to create a hierarchy with one base class and a finite number of descendants.
class figure {
public:
virtual ~figure() = default;
virtual int square() = 0;
};
struct rectangle final : public figure {
int square() override {
return 2 * width + 2 * height;
}
int width, height;
};
struct circle final : public figure {
int square() override {
return 2 * M_PI * radius;
}
int radius;
};
The function to get the area of any of the figures can be described as follows
int square(const figure& f) {
if (typeid(f) == typeid(rectangle)) {
return static_cast<rectangle&>(f).square();
} else if (typeid(f) == typeid(circle)) {
return static_cast<rectangle&>(f).square();
} else {
assert(false);
}
}
Since virtual functions, RTTI are used, this leads to code performance degradation.
As an output, it could use std::variant and std::visit.
The disadvantage of std::variant is that polymorphism must be abandoned.
https://en.cppreference.com/w/cpp/utility/variant
If the class hierarchy cannot be abandoned, CRTP can be used.
But this approach also has disadvantages:
- no redefinition of virtual functions;
- it is not easy to read the code compared to conventional virtual inheritance;
- possible binary bloat due to templates.
https://en.cppreference.com/w/cpp/language/crtp
Propose to add the sealed keyword, which can be used to mark the base (abstract) class.
It indicates that the given base (abstract) class has a finite number of descendants.
And instead of a virtual table under the hood, compiler could use a static array of descendants.
class figure sealed { // similar - final
public:
virtual ~figure() = default;
virtual int square() = 0;
};
Since the compiler knows a finite number of descendants, it can optimize the code more strongly.
For example, do not use RTTI at all. Or make typeid work at compile time.
int square(const figure& f) {
if (typeid(f) == typeid(rectangle)) {
return static_cast<rectangle&>(f).square();
} else if (typeid(f) == typeid(bad_figure)) { // compile time error: no branch with `circle`
return static_cast<rectangle&>(f).square();
}
}
Also it don't need to write an else branch. If you skip at least one of the descendants in the if-else-if chain, then the compiler at compile time could tell, which successor was skipped.
Similar classes have been added to Java, Kotlin, Scala.
https://docs.oracle.com/en/java/javase/15/language/sealed-classes-and-interfaces.html
https://kotlinlang.org/docs/sealed-classes.html
sealed classes are similar to enum types in Rust, Swift.
https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html
https://docs.swift.org/swift-book/LanguageGuide/Enumerations.html
This is similar to std::variant, but only at the language level. I very want to see such a feature in C++.
4.2.1 should also address what happens if class contains both styles of a single function with the same "this" type. e.g.
struct X {
void bar() &&;
void bar(this X&&);
};
I guess this should be ill-formed, unable to resolve overloading? What about mixing forms:
struct X {
void bar() &;
void bar() const &;
void bar(this X&&);
};
If I read your proposal right, this should be fine. Would be nice to acknowledge it.
Finally what about
struct X {
void bar(this X&);
void bar(this const X&);
void bar(this X&&);
template void bar(this S&&);
};
4.2.4 Example with the body of function f5 returning field "self.B::i" is obviously the weak point of this proposal, due to lack of succinct syntax to do an obvious thing. It is also missing examples of bugs that the user might encounter when they forget the rules - it appears that the worst that can happen is compilation error, but would be good to expand on this.
4.2.5 bad formatting in the list "Types are as follows" :^) Also, this might be a good place to refer to unified call syntax, perhaps we can reconsider making a pointer to member function callable? If so that would be different paper, obviously.
5.3 lambda example, I think the following would work as well:
// this proposal
auto fib = [](this auto self, int n) {
if (n < 2) return n;
return self(n-1) + self(n-2);
};
Since there is no capture in this particular lambda, there should be no need for "const&" (or "&&") in deduced this parameter. Perhaps this could be extended upon in section 5.4 as well (add 5.4.3 for capture-less lambdas?)
The other lambda example (tree traversal), I suggest adding some definition of Leaf at the top because we are accustomed to self-contained examples, e.g.:
using Leaf = int;
The part "self isn't the lambda, self is the overload wrapper" is IMO rather novel and a great point to score, so rather than fancy graph-like comment I'd prefer if that interaction between lambda with explicit this parameter and generic overload was explored in more depth.
I've been using range-v3 partial_sum
and python3 accumulate
somewhat often, and I've come to conceptualize it as a fold_transform
because what I'm really trying to do is an adjacent_transform
where the output is shifted rather than the input.
Food for thought
The links from README.md give us a page with the proposal source. Which is basically a lot of boilerplate CSS before we get anywhere.
Instead, the links should lead to the HTML of the proposal, with a text/HTML MIME type, so that the browser would actually display it.
Currently P2602 doesn't remove the "poison pill" for iter_swap
, but it seems that @CaseyCarter has been considering constraining std::iter_swap
for a long time (see ericniebler/stl2#264 and microsoft/STL#2799 (comment)).
So should we consider constraining it in next revision of P2602?
From Tim:
static_cast
in func.bind.bind is missing a td_i
(the thing we're casting)Just pasting my reply here so we don't forget
Hi Peter,
Thanks for reviewing our examples!
Responses inline.
On Wed, Jul 22, 2020 at 8:28 AM Peter Sommerlad [email protected] wrote:
Hi all,
Sorry for jumping very late on the bus, but as always, there is too much
happening in WG21 to be able to pay attention everywhere
I know how you feel :).
I would like to talk you into providing consistent ref-qualification in
all your code examples returning references/pointers/iterators to the
guts of an object, so that the chance for returning a referring thing to
a temporary, that will immediately dangle, is reduced.(*)
We'll do that in all examples where it makes sense - in some, we try to critique the current status quo, which is wrong, as you rightly point out.
You do that in cases of std::optional, but not in the other examples
i.e.
class TextBlock {
public:
char const& operator[](size_t position) const {
// ...
return text[position];
}
char& operator[](size_t position) {
return const_cast<char&>(
static_cast<TextBlock const&>
(this)[position]
);
}
// ...
};
should read imho:
class TextBlock {
public:
char const& operator[](size_t position) const & {
// ...
return text[position];
}
char& operator[](size_t position) & {
return const_cast<char&>(
static_cast<TextBlock const&>
(this)[position]
);
}
// ...
};
Yes, that should be fixed, IMO. However, you will note that with deducing this, it's impossible to write the un-ref qualified pathological cases. I believe this improves safety.
That leads to the question, how to specify the 'this Self&& self'
parameter in your replacement, because calling it on a temporary would
lead to deducing an rvalue reference, this returning a reference to a
temporary.
Yes, the deduction to rvalue reference is a feature, not a bug. That said, the guidance for what to return from functions called on temporaries would do well to take your point under advisement.
So should this read then instead like the following?
class TextBlock {
public:
template <typename Self>
auto& operator[](this Self& self, size_t position) {
// ...
return self.text[position];
}
// ...
};
No, it should not read as Self& - you can't deduce const& from Self&. I think a more plausible alternative is something like this, if you want to return by value for temporarires:
template <typename Self, typename Return>
using nodangle_forward_ret_t = std::conditional_t<std::is_lvalue_reference_v<Self&&>, std::copy_cvref_t<Self&&, Return>, Return>;
class TextBlock {
public:
template
auto operator[](this Self&& self, size_t position)
-> nodangle_forward_ret_t<decltype(self), char> {
// ...
return std::forward(self).text[position];
}
// ...
};
copy_cvref_t is proposed in this mailing: http://wg21.link/p1450
I believe this highlights a point - for code that wants to return by value for temporaries instead of returning a rvalue-reference for such accessors (which IMO is legitimate, but a teensy bit more likely to dangle), we should probably standardize a convenient type-trait and forwarder so we can have the idiomatic usage be easy.
Looking at cppreference, the implementations of the std::make_unique() and std::make_shared() functions use a simple call of the new operator.
https://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique
https://en.cppreference.com/w/cpp/memory/shared_ptr/make_shared
And the implementations of the three main compilers confirm this.
https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/unique_ptr.h
https://github.com/llvm/llvm-project/blob/main/libcxx/include/__memory/unique_ptr.h
https://github.com/microsoft/STL/blob/main/stl/inc/memorу
Using std::make_unique() and std::make_shared() can result to a std::bad_alloc exception, being thrown if there is not enough memory in system.
In an environment where exceptions are disabled, it must use the new operator with std::nothrow.
std::unique_ptr<T> p = new(std::nothrow) T();
The std::make_shared() and std::make_unique() functions are more efficient and could prevent double allocations.
It is proposed to add std::make_unique_nothrow() and std::make_shared_nothrow() functions, which could be implemented as follow.
template <class T, class... Args>
std::unique_ptr<T> make_unique_nothrow(Args&&... args)
noexcept(noexcept(T(std::forward<Args>(args)...)))
{
return std::unique_ptr<T>(new (std::nothrow) T(std::forward<Args>(args)...));
}
template <class T, class... Args>
std::shared_ptr<T> make_shared_nothrow(Args&&... args)
noexcept(noexcept(T(std::forward<Args>(args)...)))
{
return std::shared_ptr<T>(new (std::nothrow) T(std::forward<Args>(args)...));
}
The boost library already implements similar functions.
https://www.boost.org/doc/libs/1_63_0/boost/move/make_unique.hpp
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.