C++ Logo

std-proposals

Advanced search

Re: [std-proposals] std::elide

From: Lorand Szollosi <szollosi.lorand_at_[hidden]>
Date: Sun, 26 May 2024 03:01:41 +0200
Hi,

> I've been told time and time again, that no matter how good an idea
> you have to change C++; your idea must achieve something that isn't
> already possible, otherwise it isn't worth the time, effort, grief and
> hassle of writing it into the Standard and having the compiler vendors
> implement it.
Indeed, my wording in the long mail perhaps could have been better, let me
correct it.
That mail had 2 parts:
- in the first part, I described how getLockedSomething() can be
implemented *without changing the current standard*, in terms of structured
binding. Of course, the optional version works as well, if we allow the
definition and setup to be distinct.
- in the second part, I described an alternative schematics to solve both
the above described problem and other real-world problems I regularly
encounter. Some of these problems were described on the thread, hence the
brainstorming.

The real-world problems are:
1. how to return a char const* to a callee-created char const[fixed_N] that
then the caller deallocates (e.g. mocks, char-to-wchar API strings, etc.)
2. how to have a make function for a non-movable class that can fail,
without throwing exceptions
3. an alternative with way less noise for in-class description (e.g. field
names in serialization, optionality, etc.)
4. how to do a post-constructor setup on a non-movable class without heap
allocation

Detailed:
1. Normally, you'd return a std::string, but when writing test system
mocks, you need to match the return type of a function (to test exactly the
same thing). The char[N] for fixed N in the mock is produced but not
stored, thus you need to keep it alive when returning char*, however, you'd
also like to avoid leaking (and function static, ever-growing storage as
well).
When this is solved, it also simplifies conversion functions like
char-to-wchar, without the need of output parameters.
2. Semaphores (which are non-movable) in a service class. The service class
has a private/keyed ctor as it has preconditions and we try to avoid
exceptions (and also avoid returning an instance without establishing the
invariant); thus a static named ctor is used that returns std::optional<SC>
or std::expected<SC, Err> or similar. Note that I am aware you could take a
reference as an output parameter (or even the address of it as a template
arg, if instance were global), which I consider as a workaround with
limitations (e.g., can't be a prvalue in the middle of an expression,
cmiiw).
3. Tagged serialization (sql, yaml, json, etc.) needs a field name for each
member of the class. This might or might not be the same as the member
name, thus even with reflexpr, we might need a field name tag. Currently,
you need to write something (static template fn on &T::Field, macro,
operator<< and operator>>, etc.) for each member to be serialized; this
would be reduced to, e.g., int uid = 1 @ "User";.
4. (Perhaps this is the weakest reason, as it's code style) Third-party API
factory class, cannot be changed, non-movable. Creates an instance of a
service class, both must be kept alive (service registers itself to
factory), service also needs connect() to be called. Idea is to return a
connected service from a function in a single step, you want to ensure that
non-connected instances are not returned to the client.

Suggested solutions: (syntax is preliminary)
1.
char const* @ auto MockS::collectSomeData(...) const {
    char const* s = _strdup(/* generate/collect/convert string */);
    return s @ scope_exit([s]() { free s; });
}

2.
// non-movable SC, sc_key{} can only be constructed by SC::make

/*static*/ std::optional<SC> @ auto SC::make(auto const& args...) {
    return std::optional<SC>(sc_key{}, args...) @ [&retval=@@] {
        // here, init and post_constructor_cheks might use the semaphore in
SC;
        retval->init();
        if (post_constructor_checks(*retval)) {
            retval->reset();
        }
    }();
}

3.
struct User {
    int uid = 0 @ "User";
    int pin @ "PIN";
};

then, in (de)serialization of User u, @u.uid is "User" and @u.pin is "PIN".
Ideally, you don't need to write field names, either: you can write use
structured binding and thus have a generic serializer/deserializer.

4.
class apiFactory; // has makeService() member function that returns
apiService
class apiService; // has connect()

// this creates apiFactory with auto storage duration of _caller_
// calls makeService() on it, the result of which will be the return value
(due to @ @ prefix)
// but first calls connect() on it. This is due to a @ b being
right-associative, but evaluated left-to-right,
// @ @ ( a @ b @ c @ d ) is always c @ d, with 'a' going out of scope on
return and b kept in caller's auto storage
apiService getConnectedService() { return @ @ (0 @ apiFactory() @
@@.makeService() @ @@.connect()); }


Hope that helps to clear up any confusion caused by my side,
Best regards,
-lorro

On Sat, May 25, 2024 at 9:40 PM Frederick Virchanza Gotham via
Std-Proposals <std-proposals_at_[hidden]> wrote:

> On Sat, May 25, 2024 at 8:13 PM Lorand Szollosi wrote:
> >
> > Indeed - my point here was, if Frederick is okay with
> > auto [v] = getLockedWhatever(); syntax, as in the
> > example with get<> override, then getLockedWhatever
> > can be a struct with a ctor and a public member.
>
>
> I've been told time and time again, that no matter how good an idea
> you have to change C++; your idea must achieve something that isn't
> already possible, otherwise it isn't worth the time, effort, grief and
> hassle of writing it into the Standard and having the compiler vendors
> implement it.
>
> So if we start off with the following program that needs NRVO:
>
> #include <mutex>
> using std::mutex;
>
> mutex Func(void)
> {
> mutex m;
> m.lock();
> return m; // compiler error
> }
>
> int main(void)
> {
> mutex m = Func();
> }
>
> Well if you ask me whether this is already possible in C++, well I'd
> have to say.... yes it is, if you use an "std::optional" as follows.
> The std::optional can be a global variable with static duration, or it
> can be on the heap, or on the stack, wherever you want it. As follwos:
>
> #include <cassert>
> #include <mutex>
> #include <optional>
> using std::mutex, std::optional;
>
> void Func(optional<mutex> &m)
> {
> assert( false == m.has_value() );
>
> try
> {
> m.emplace();
> m->lock();
> }
> catch(...)
> {
> m.reset();
> throw;
> }
> }
>
> int main(void)
> {
> optional<mutex> m;
> Func(m);
> }
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>

Received on 2024-05-26 01:01:55