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@lists.isocpp.org> 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@lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals