Discussion:
[std-proposals] Adding the Keyword “proxy” in C++ [update for the "interface"]
Mingxin Wang
2017-05-10 10:46:22 UTC
Permalink
This is an update version for another topic “Adding the Keyword “interface”
in C++
<https://groups.google.com/a/isocpp.org/forum/#!topic/std-proposals/uyfpeyEyW4o>”
I started days before, which was an idea about automatically generating new
types which are implicitly convertible from any type that meets some
specific requirements. In this post, according to the feedbacks, two
adjustments are made to make the solution more reasonable comparing to the
initial version:

- Changing the keyword “interface” into “proxy” (*thanks to Thiago
Macierira, Myriachan and Jakob Riedle*), and
- Changing the way of passing the concrete implementations by value into
by reference, so that polymorphism and lifetime management are decoupled (*thanks
to Bengt Gustafsson*).

The rules are not changed in defining “proxies” (“interface” in the initial
post):

proxy Runnable {

void operator()();

};

It is only allowed to initialize a proxy object with reference (lvalue):

auto lambda = [] { puts("Lambda"); };
Runnable r(lambda);
r();

The code below is ill-formed now (but leagal in the initial version):

Runnable r([] { puts("Lambda"); });

Here is a possible implementation to generate for the proxy defined above
(decouples polymorphism and lifetime management comparing to the initial
version, and no dynamic memory allocation is required any more):

class Runnable {
public:
template <class Data>
Runnable(Data& data) requires requires(Data& data) { { data() }; } {
Implementation<Data> a(data);
memcpy(&data_, &a, sizeof(Abstraction));
}
Runnable() = default;
Runnable(Runnable&&) = default;
Runnable(const Runnable&) = default;
Runnable& operator=(Runnable&&) = default;
Runnable& operator=(const Runnable&) = default;

void operator()() { reinterpret_cast<Abstraction*>(&data_)->op_0(); }

private:
class Abstraction {
public:
Abstraction(void* data) : data_(data) {}
virtual void op_0() = 0;
template <class T>
T* get() { return static_cast<T*>(data_); }

private:
void* data_;
};

template <class Data>
class Implementation final : public Abstraction {
public:
Implementation(Data& data) : Abstraction(&data) {}
void op_0() override { (*get<Data>())(); }
};

char data_[sizeof(Abstraction)];
};

I am looking forward to your comments and suggestions!

Thank you!

Mingxin Wang
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/974213b5-2afd-4c05-8ee2-d06de3e3cdd6%40isocpp.org.
Jakob Riedle
2017-05-10 12:10:35 UTC
Permalink
Post by Mingxin Wang
- Changing the keyword “interface” into “proxy” (*thanks to Thiago
Macierira, Myriachan and Jakob Riedle*), and
I did not vote for this :)
There's no reason to have both. They are the same thing. With Concepts TS,
it's just a constraint placed on types for function templates and class
templates. What we're talking about here is more runtime polymorphism - but
the interface we're defining is exactly the same thing. We don't need two
keywords with two sets of rules.
To give a little history of what *proxies/**interfaces *are, I quote Bjarne
[A History of C++: 1979−1991]*:*

For many people, the largest single problem using C++ is the lack of an
Post by Mingxin Wang
extensive standard
library. A major problem in producing such a library is that C++ does not
provide a sufficiently
general facility for defining ‘‘container classes’’ such as lists,
vectors, and associative
arrays.
There are two approaches for providing such classes/types: One can either
rely on dynamic typing
and inheritance like Smalltalk does, or one can rely on static typing and
a facility for arguments
of type type. The former is very flexible, but carries a high runtime
cost, and more importantly
defies attempts to use static type checking to catch interface errors.
Therefore, the latter approach
was chosen.
Parameterization as It is in C++ is just one possible approach. *Both
Polymorphism and Parameterization have their drawbacks and their
advantages.*
C++ has just not (yet) explored the principle of dynamic generic
programming in the form of polymorphism.
For clarification and for sake of argument, that it is important and well
thought to fill this gap, I created the following diagram:

<Loading Image...>
Post by Mingxin Wang
- Changing the way of passing the concrete implementations by value
into by reference, so that polymorphism and lifetime management are
decoupled (*thanks to Bengt Gustafsson*).
I replied to this topic, that it would be important, If we want to treat
dynamic concepts identically to static concepts, that they behave
identically to values of the concrete type:

std::function<void()> f1 = /**/;
Post by Mingxin Wang
std::function<void()> f2();
const std::function<void()> f3 = /**/;
Callable<void()> c1 = f1; // This copies f1. Internally, an lvalue object
of type std::function<void()> is held.
Callable<void()>& c2 = f1; // This references f1. Internally, an lvalue
reference (std::function<void()>&) is stored.
Callable<void()>&& c3 = f2(); // Internally stored as '
std::function<void()>&&'.
Callable<void()> c4 = f2(); // This moves the result of f2 into c4.
Internally stored as real std::function<void()> lvalue .
const Callable<void()>& c4 = f3; //Internally 'const &' as handle.
// and so on...
Again, my answer assumed, that "proxies" or "interfaces" just introduce a
lot of redundancy to concepts
and therefore tries to come up with a solution that *reuses the concepts we
already have.*
*I hope this does not sound to rude Mingxing Wang, I'm trying to be
pragmatic.*

IMHO I don't think it is important at this point to discuss about a
*specific* Implementation yet.
As you have read in the quote from Bjarne, Polymorphism as Solution for
Generic Programming has been considered to be feasible :-)
However what I think is important to do right now is two things:

1. Come up with an elegant SYNTAX:
I think here it is important to not reinvent the wheel of concepts at
runtime but rather *expand* concepts to runtime.
2. Come up with an appropriate SEMANTICS (incl. Ownership management):
Here I think we can either have a *different *rule set compared to
how concepts names are used right now (as a placeholder for the real type)
or rather adopt this syntax for runtime polymorphism. The latter
would BTW mean to defer ownership management outside of the concept!
(Using ref/const etc. qualifiers *around *the concept name).

Either way, we have to be clear what we are doing and why we are doing it.

Yours,
Jakob

PS: Independently of what I said above, I think the meat of the topic you
brought
up is very important and I find it really good that you're pursuing it!
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/5f6a0f96-8424-4853-9c09-3607db9f6443%40isocpp.org.
Mingxin Wang
2017-05-10 17:14:00 UTC
Permalink
Post by Jakob Riedle
I replied to this topic, that it would be important, If we want to treat
dynamic concepts identically to static concepts, that they behave
Again, my answer assumed, that "proxies" or "interfaces" just introduce a
Post by Jakob Riedle
lot of redundancy to concepts
and therefore tries to come up with a solution that *reuses the concepts we
Post by Jakob Riedle
already have.*
As I said before, the "proxies" is a use case to the "concepts", and is not
a runtime alternative, because of, from my point of view, two reasons:

*The first reason is: There is little overlap between the scenarios for
using the two mechanisms.*

The "concepts" is usually used when calling a function template (or storing
in a class template independently) to ensure the input types to be
well-formed. The "proxies" can be used when it is required to store
different implementations with a uniform format.

For example, the "Lockable requirements" is defined in C++ standard as:

<Loading Image...>


This term is required in many library functions, e.g., "std::lock" and
"std::try_lock":

<Loading Image...>


The "concepts" can be used for these functions, but the mutexes are not
required to be stored with a uniform format. Thus it requires the
"concepts" rather than the "proxies" here.

There is another example I posted earlier that requires the "proxies"
rather than the "concepts":

For example, suppose we want to store some iterators (may have different
Post by Jakob Riedle
types) in a container and perform some operations with them (only
"operator*", "operator++" and "operator==" are used), we may write the
template <class T>
Post by Jakob Riedle
*proxy* It {
T operator*();
void operator++(); // The returned value is not required
bool operator==(const It&);
};
std::vector<It<int>> vec;
for (It<int> it : vec) { ... }
What if we build another wrapper type that implements everything specified
in the "ranges TS"? Let's see what is defined in
"std::experimental::ranges::Iterator" first:

template <class I>
concept bool Iterator() {
return ranges::WeaklyIncrementable<I>()
&& requires(I i) {
{ *i } -> auto&&; // Requires: i is dereferenceable
};
}

So, what is "ranges::WeaklyIncrementable<I>"? It is defined as follows:

concept bool WeaklyIncrementable() {
return ranges::Semiregular<I>()
&& requires(I i) {
typename ranges::difference_type_t<I>;
requires
ranges::SignedIntegral<ranges::difference_type_t<I>>();
{ ++i } -> ranges::Same<I&>; /* not required to be equality
preserving */
i++; /* not required to be equality preserving */
};
}

It depends on another concept again! So what exactly does concept
"std::experimental::ranges::Iterator" defines?

template <class T>
concept bool Destructible() {
return requires(T t, const T ct, T* p) {
{ t.T() } noexcept;
{ &t } -> ranges::Same<T*>; /* not required to be equality
preserving */
{ &ct } -> ranges::Same<const T*>; /* not required to be
equality preserving */
delete p;
delete[] p;
};
}

template < class T, class... Args >
concept bool __ConstructibleObject = /* exposition only */
ranges::Destructible<T>() && requires(Args&&... args) {
T{ std::forward<Args>(args)... };
new T{ std::forward<Args>(args)... };
};

template < class T, class... Args >
concept bool __BindableReference = /* exposition only */
std::is_reference<T>::value && requires(Args&&... args) {
T( std::forward<Args>(args)... );
};

template < class T, class... Args >
concept bool Constructible() {
return __ConstructibleObject<T, Args...> || __BindableReference<T,
Args...>;
}

template < class T, class U >
concept bool ConvertibleTo() { return std::is_convertible<T, U>::value; }

template <class T>
concept bool MoveConstructible() {
return ranges::Constructible<T, std::remove_cv_t<T>&&>() &&
ranges::ConvertibleTo<std::remove_cv_t<T>&&, T>();
}

template <class T>
concept bool CopyConstructible() {
return ranges::MoveConstructible<T>() &&
ranges::Constructible<T, const std::remove_cv_t<T>&>() &&
ranges::ConvertibleTo<std::remove_cv_t<T>&, T>() &&
ranges::ConvertibleTo<const std::remove_cv_t<T>&, T>() &&
ranges::ConvertibleTo<const std::remove_cv_t<T>&&, T>();
}

template < class T, class U >
concept bool CommonReference() {
return requires(T (&t)(), U (&u)()) {
typename std::common_reference_t<T, U>;
typename std::common_reference_t<U, T>;
requires ranges::Same<std::common_reference_t<T, U>,
std::common_reference_t<U, T>>();
std::common_reference_t<T, U>(t());
std::common_reference_t<T, U>(u());
};
}

template <class T, class U>
concept bool Assignable() {
return ranges::CommonReference<const T&, const U&>() &&
requires(T&& t, U&& u) {
{ std::forward<T>(t) = std::forward<U>(u) } ->
ranges::Same<T&>;
};
}

template <class T>
concept bool Swappable() {
return requires(T&& a, T&& b) {
ranges::swap(std::forward<T>(a), std::forward<T>(b));
};
}

template <class T, class U>
concept bool Swappable() {
return ranges::Swappable<T>() &&
ranges::Swappable<U>() &&
ranges::CommonReference<const T&, const U&>() &&
requires(T&& t, U&& u) {
ranges::swap(std::forward<T>(t), std::forward<U>(u));
ranges::swap(std::forward<U>(u), std::forward<T>(t));
};
}

template <class T>
concept bool Movable() {
return ranges::MoveConstructible<T>() &&
ranges::Assignable<T&, T>() &&
ranges::Swappable<T&>();
}

template <class T>
concept bool Copyable() {
return ranges::CopyConstructible<T>() &&
ranges::Movable<T>() &&
ranges::Assignable<T&, const T&>();
}

template <class T>
concept bool DefaultConstructible() {
return ranges::Constructible<T>() &&
requires(const std::size_t n) {
new T[n]{}; /* not required to be equality preserving */
};
}

template <class T>
concept bool Semiregular() {
return ranges::Copyable<T>() &&
ranges::DefaultConstructible<T>();
}

template < class T >
concept bool Integral() { return std::is_integral<T>::value; }

template < class T >
concept bool SignedIntegral() { return Integral<T>() &&
std::is_signed<T>::value; }

template <class I>
concept bool WeaklyIncrementable() {
return ranges::Semiregular<I>()
&& requires(I i) {
typename ranges::difference_type_t<I>;
requires
ranges::SignedIntegral<ranges::difference_type_t<I>>();
{ ++i } -> ranges::Same<I&>; /* not required to be equality
preserving */
i++; /* not required to be equality preserving */
};
}

template <class I>
concept bool Iterator() {
return ranges::WeaklyIncrementable<I>()
&& requires(I i) {
{ *i } -> auto&&; // Requires: i is dereferenceable
};
}

A type satisfies "concept template Iterator" if and only if all the
concepts above are satisfied! Shall we turn these stuff into a runtime
wrapper? I suppose not, because that will introduce much runtime overhead,
and I prefer to define a proxy instead. After all, only 3 member functions
is required to be abstract at runtime.

*The second reason is: Some concepts are difficult to turn into specific
type requirements so far.*

It is true that we can turn some simple concepts into proxies. For example:

template <class T>
concept bool Foo() {
return requires(T t) {
{ t() };
{ t.f() } -> int;
}
}

proxy Foo {
void operator()();
int f();
}

But it is incredibly difficault for some other concepts. For example:

template <class T>
concept bool Foo() {
return requires(T t1, T t2, const T t3) {
t1.op_0(t2.op_1(t3.op_2()));
};
}

It is easy to check whether the expressions are well-formed at compile-time
for a concrete type T. But how can we abstract such type at runtime?

Mingxin Wang
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/78f335a4-a099-429d-bcd2-8fe3a9b185c1%40isocpp.org.
Michał Dominiak
2017-05-10 17:20:23 UTC
Permalink
Post by Mingxin Wang
A type satisfies "concept template Iterator" if and only if all the
concepts above are satisfied! Shall we turn these stuff into a runtime
wrapper? I suppose not, because that will introduce much runtime overhead,
and I prefer to define a proxy instead. After all, only 3 member functions
is required to be abstract at runtime.
Why would a compiler generate code for a runtime wrapper for a concept that
isn't used as a runtime wrapper at any point in the program?

If you say it'd need to do that to do runtime wrapper inheritance for all
the base concepts, then this is a particularly flawed approach. (As are
many OOP hierarchies.)
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAPCFJdTaT%3D4Km%3DpCDwknhFTKW%3DmXphMR%2Ba3mc12x3eKzfD3U8g%40mail.gmail.com.
Mingxin Wang
2017-05-11 08:32:12 UTC
Permalink
Post by Michał Dominiak
Why would a compiler generate code for a runtime wrapper for a concept
that isn't used as a runtime wrapper at any point in the program?
If you say it'd need to do that to do runtime wrapper inheritance for all
the base concepts, then this is a particularly flawed approach. (As are
many OOP hierarchies.)
Unlike concepts which can be treated as boolean constants at runtime,
proxies are real types. The same as other types, any proxy object has a
compile-time-defined data structure which requires a certain scale of
memory to be constructed at runtime.

It is possible that some expressions defined in a concept can be ignored
when building a runtime wrapper according to the concept at compile-time,
but this is only what we expect from the compiler, and no one can promise
that. Besides, since a proxy is a type, the value of a proxy may be passed
through compile units (definitions and functions maybe defined in different
source files), and the compiler may know nothing about how the value is
used in other compile units. So there are limitations for compiler
optimizations.

By the way, there is a "bug" in the possible implementation I posted, which
is a semantic error, but will not affect the correctness of the program.
Anyway, it is fixed, as is shown below:

class Runnable {
public:
template <class Data>
Runnable(Data& data) requires requires(Data& data) { { data() }; } {
Implementation<Data> a(data);
memcpy(*data_*, &a, sizeof(Abstraction));
}
Runnable() = default;
Runnable(Runnable&&) = default;
Runnable(const Runnable&) = default;
Runnable& operator=(Runnable&&) = default;
Runnable& operator=(const Runnable&) = default;

void operator()() { reinterpret_cast<Abstraction*>(*data_*)->op_0(); }

private:
class Abstraction {
public:
Abstraction(void* data) : data_(data) {}
virtual void op_0() = 0;
template <class T>
T* get() { return static_cast<T*>(data_); }

private:
void* data_;
};

template <class Data>
class Implementation final : public Abstraction {
public:
Implementation(Data& data) : Abstraction(&data) {}
void op_0() override { (*get<Data>())(); }
};

char data_[sizeof(Abstraction)];
};

*P.S. How do you think of this implementation that provides
runtime polymorphism support without dynamic memory allocation?*

Thank you!

Mingxin Wang
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/2251d51a-005e-4f85-8e20-6e89a30d708f%40isocpp.org.
Michał Dominiak
2017-05-11 08:42:26 UTC
Permalink
Post by Michał Dominiak
Why would a compiler generate code for a runtime wrapper for a concept
Post by Michał Dominiak
that isn't used as a runtime wrapper at any point in the program?
If you say it'd need to do that to do runtime wrapper inheritance for all
the base concepts, then this is a particularly flawed approach. (As are
many OOP hierarchies.)
Unlike concepts which can be treated as boolean constants at runtime,
proxies are real types. The same as other types, any proxy object has a
compile-time-defined data structure which requires a certain scale of
memory to be constructed at runtime.
It is possible that some expressions defined in a concept can be ignored
when building a runtime wrapper according to the concept at compile-time,
but this is only what we expect from the compiler, and no one can promise
that. Besides, since a proxy is a type, the value of a proxy may be passed
through compile units (definitions and functions maybe defined in different
source files), and the compiler may know nothing about how the value is
used in other compile units. So there are limitations for compiler
optimizations.
The person writing the wording for the feature can promise that.
Post by Michał Dominiak
By the way, there is a "bug" in the possible implementation I posted,
which is a semantic error, but will not affect the correctness of the
class Runnable {
template <class Data>
Runnable(Data& data) requires requires(Data& data) { { data() }; } {
Implementation<Data> a(data);
memcpy(*data_*, &a, sizeof(Abstraction));
}
Runnable() = default;
Runnable(Runnable&&) = default;
Runnable(const Runnable&) = default;
Runnable& operator=(Runnable&&) = default;
Runnable& operator=(const Runnable&) = default;
void operator()() { reinterpret_cast<Abstraction*>(*data_*)->op_0(); }
class Abstraction {
Abstraction(void* data) : data_(data) {}
virtual void op_0() = 0;
template <class T>
T* get() { return static_cast<T*>(data_); }
void* data_;
};
template <class Data>
class Implementation final : public Abstraction {
Implementation(Data& data) : Abstraction(&data) {}
void op_0() override { (*get<Data>())(); }
};
char data_[sizeof(Abstraction)];
};
*P.S. How do you think of this implementation that provides
runtime polymorphism support without dynamic memory allocation?*
Thank you!
Mingxin Wang
--
You received this message because you are subscribed to the Google Groups
"ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an
To view this discussion on the web visit
https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/2251d51a-005e-4f85-8e20-6e89a30d708f%40isocpp.org
<https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/2251d51a-005e-4f85-8e20-6e89a30d708f%40isocpp.org?utm_medium=email&utm_source=footer>
.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAPCFJdRZvT5MZsfvo4QhVWYVM_hmUgJuaiUK-MDUv5Y4ZhHgyg%40mail.gmail.com.
Jakob Riedle
2017-05-12 09:59:53 UTC
Permalink
I'll write here in the behalf of hopefully most people.

Apparently, runtime polymorphism is considered to have benefits for the
language, given it can be implemented and specified properly.
By that I mean, that a potential Proposal needs to adress the following
concerns:

1. Overhead w.r.t code size
2. Overhead w.r.t compile time
3. In what way can and should predefined Concepts be reused to generate
abstract classes that forward functionality to concrete types? (possibly
not at all?)
4. Ensure Type safety across all compilation units
5. Ensure proper ownership management
6. Should "Static" concepts behave like "dynamic" ones only in that they
operate at compile time? Why is a different approach preferable?

If anyone wants to write a proposal, they are invited to do so.
In case I missed something really important, feel free to add stuff.
Remember: This list is not about personal design preferences, but about
criteria to be discussed.

Have a nice Weekend,
Jakob
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/03f03fa0-aa65-4abf-832e-abbeba5466a8%40isocpp.org.
Nicol Bolas
2017-05-12 14:49:48 UTC
Permalink
Post by Jakob Riedle
I'll write here in the behalf of hopefully most people.
Apparently, runtime polymorphism is considered to have benefits for the
language, given it can be implemented and specified properly.
By that I mean, that a potential Proposal needs to adress the following
1. Overhead w.r.t code size
2. Overhead w.r.t compile time
3. In what way can and should predefined Concepts be reused to
generate abstract classes that forward functionality to concrete types?
(possibly not at all?)
4. Ensure Type safety across all compilation units
5. Ensure proper ownership management
6. Should "Static" concepts behave like "dynamic" ones only in that
they operate at compile time? Why is a different approach preferable?
*1:* Code size should be about what you would expect from any form of
type erasing system like `any` or `function`. The code size increase will
be based on two factors:

A) The number of operations that the "abstract class" exposes.
B) The number of types that get erased.

*2:* As we move closer to getting modules, I think this becomes less
relevant. In a modularized codebase, whatever the code generation will be,
it will generally only happen once (or perhaps a few times). So even if it
is somewhat expensive (and I don't think it will be), modules can make it
cheap.

*3:* The expressiveness of concepts (particularly through arbitrary code
execution) means that it is impossible to guarantee that a type-erased
class can exactly match the functionality of a concept.

But at the same time, the ability to convert a concept into a type-erased
type would be exceedingly useful. But as previously stated, it's not
possible to do this in general due to the complexities of concept
definitions. So if we're going to use concepts as the basis for this, we
need a way for users to know which concept constructs will be converted and
which will not.

For example, can we convert a requires statement like this:

{adl_func(val) + int()} -> int;

Into some kind of type-erased forwarding code? Because that's two separate
operations that need to be wrapped: the call to `adl_func` and the
`operator+` between the return value of that and `int()`. The problem here
is that the return type of `adl_func` is not known, since it depends on
`type`. But the wrapper functions have to be static; they can't be
generated at the time we apply the type to the type-erased function. So
it's not clear how that could work.

It may be that the complexity of such conversions is too great, and the
only effective solution would be to provide an alternative system for
specifying which operations are converted and how those conversions work.

*4:* I don't see how that's a problem. What we're talking about is having
the compiler generate a type with certain properties and functions. It
should have a well-defined typename, and it should follow the
one-definition-rule. So if we generate this type from a concept, then the
ODR would apply: if you use a different concept with the same typename in
different translation units, you violate the ODR.

*5:* Yes, that's important. We need to be able to specify at least the
following kinds of behavior with respect to the type-erased object:
reference semantics, value semantics but with only move support, and value
semantics but with copy support.

*6:* Even if we go with a concepts-based approach to the code generation,
you should not use a concept *directly* when you actually mean to use the
type-erased type. They ought to have different names, so it is immediately
clear to everyone when you're using a concept and when you're using a
type-erased type.

After all, there's no way to achieve #5 if you make it some kind of
contextual thing, where a concept can refer to a typename in some cases and
a concept in others. In order for the user to specify ownership behavior,
there has to be some form of explicit declaration of that intent. And two
separate users of the same concept ought to be able to specify different
ownership behavior. And that requires each user to use a distinct name for
the combination of concept+behavior.

And that doesn't cover the ambiguities created by trying to make a concept
name also be a typename. The use as a function argument, for example: how
do you tell if the user wants it to be a template function or a regular
function? Or the use as a placeholder type:

ConceptName var = expr;

Should `decltype(var)` be `ConceptName`, or should it be `decltype(expr)`?

This ambiguity doesn't exist if we give the type-erased version a specific
name that is distinct from the concept name.

Oh sure, you could probably come up with a keyword or operator that, when
coupled with a concept name will resolve the ambiguity. But using an actual
typename for the type-erased type makes things so much simpler.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/a1f06dc9-6455-4c05-8673-5812d141f0e3%40isocpp.org.
Hyman Rosen
2017-05-12 15:08:25 UTC
Permalink
In a modularized codebase, whatever the code generation will be, it will
generally only happen once (or perhaps a few times).
Really? Why? Modules control visibility of names, AFAIK. How do they
affect code generation?
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAHSYqdYasjNipjjRZwLe%3DV-djuPpaK4wCx4kO6aXO_iigfNPPg%40mail.gmail.com.
Nicol Bolas
2017-05-12 15:16:26 UTC
Permalink
Post by Hyman Rosen
In a modularized codebase, whatever the code generation will be, it will
generally only happen once (or perhaps a few times).
Really? Why? Modules control visibility of names, AFAIK. How do they
affect code generation?
A module functionally represents an AST. If a module exports a type-erased
type, then it is effectively exporting the AST for that type-erased type.
Which means that it must export the code generated by the system that
generated that type-erased type. And therefore, any code generation needed
for that AST must have been performed. Therefore, users that include that
module don't need to perform code generation for it.

It's no different from exporting a virtual base class in that regard. The
module will export whatever is needed to make that type function: the
definitions of any virtual functions, the vtable arrangement, and so forth.
If you import a module containing a virtual base class, you shouldn't
expect the compiler to re-compile those virtual function definitions or
build a vtable for the type or other such things.

This kind of thing is *the whole point* of modules.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/4189326d-92bf-4cbc-8518-a9209a889dda%40isocpp.org.
Hyman Rosen
2017-05-12 16:05:33 UTC
Permalink
Post by Nicol Bolas
A module functionally represents an AST. If a module exports a type-erased
type, then it is effectively exporting the AST for that type-erased type.
Which means that it must export the code generated by the system that
generated that type-erased type. And therefore, any code generation needed
for that AST must have been performed. Therefore, users that include that
module don't need to perform code generation for it.
But modules can export templates, can't they? The *Runnable* class above
has them. Users that include the module containing that class will still
need to generate code for the specializations they instantiate. Am I
missing something?
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAHSYqdbbP%3DqVkfeXiYXqOhAqQxqKtHmv80G%3Dsrk-gre0RTHTEQ%40mail.gmail.com.
i***@gmail.com
2017-05-12 18:20:56 UTC
Permalink
Post by Hyman Rosen
Post by Nicol Bolas
A module functionally represents an AST. If a module exports a
type-erased type, then it is effectively exporting the AST for that
type-erased type. Which means that it must export the code generated by the
system that generated that type-erased type. And therefore, any code
generation needed for that AST must have been performed. Therefore, users
that include that module don't need to perform code generation for it.
But modules can export templates, can't they? The *Runnable* class above
has them. Users that include the module containing that class will still
need to generate code for the specializations they instantiate. Am I
missing something?
Yes, but this will happen only once for each unique module that use that
template not for all translation units like today. More modular code will
be then less repetition work will be done.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/65947dca-3a28-4190-b6c0-e44d529a0807%40isocpp.org.
Hyman Rosen
2017-05-12 18:27:00 UTC
Permalink
Post by i***@gmail.com
Post by Hyman Rosen
But modules can export templates, can't they? The *Runnable* class
above has them. Users that include the module containing that class will
still need to generate code for the specializations they instantiate. Am I
missing something?
Yes, but this will happen only once for each unique module that use that
template not for all translation units like today. More modular code will
be then less repetition work will be done.
This sounds like you're assuming that one module will consist of many
translation units, and that somehow compiling those many translation units
won't require that each one have the template code generated into it. What
is the basis for believing that?
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAHSYqdaaovNHmvucbW%3Dbd9Hd8UihjkCNdsPjZ4MLAH-CdoLEcw%40mail.gmail.com.
i***@gmail.com
2017-05-12 18:41:55 UTC
Permalink
Post by Hyman Rosen
Post by i***@gmail.com
Post by Hyman Rosen
But modules can export templates, can't they? The *Runnable* class
above has them. Users that include the module containing that class will
still need to generate code for the specializations they instantiate. Am I
missing something?
Yes, but this will happen only once for each unique module that use that
template not for all translation units like today. More modular code will
be then less repetition work will be done.
This sounds like you're assuming that one module will consist of many
translation units, and that somehow compiling those many translation units
won't require that each one have the template code generated into it. What
is the basis for believing that?
Look on current code bases, all code in .h file can be changed to modules.
This mean all instantiation of templates that currently was done in headers
will be done only once. Instantiation in .cpp will be repeated for each
separate file. Even if module need multiple translation units to compile
itself then we will only pay for them not for all translation units that
use that module. You could even crate small auxiliary modules that will
instantiation one class that will be used in other modules.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/ef10cf93-0dd8-457c-b74e-e891647b031a%40isocpp.org.
Hyman Rosen
2017-05-12 19:20:23 UTC
Permalink
Post by i***@gmail.com
Look on current code bases, all code in .h file can be changed to modules.
This mean all instantiation of templates that currently was done in headers
will be done only once. Instantiation in .cpp will be repeated for each
separate file. Even if module need multiple translation units to compile
itself then we will only pay for them not for all translation units that
use that module. You could even crate small auxiliary modules that will
instantiation one class that will be used in other modules.
Well, maybe. Except that compiler customers also want aggressive inlining
to happen, so there may be less benefit than you expect.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAHSYqdbw%2ByiYMAh-HHs1Epat4kdkgy0Pwq0%2BvEe0%3D2yd_yB13Q%40mail.gmail.com.
Ville Voutilainen
2017-05-12 19:50:39 UTC
Permalink
Post by Hyman Rosen
Post by i***@gmail.com
Look on current code bases, all code in .h file can be changed to modules.
This mean all instantiation of templates that currently was done in headers
will be done only once. Instantiation in .cpp will be repeated for each
separate file. Even if module need multiple translation units to compile
itself then we will only pay for them not for all translation units that use
that module. You could even crate small auxiliary modules that will
instantiation one class that will be used in other modules.
Well, maybe. Except that compiler customers also want aggressive inlining
to happen, so there may be less benefit than you expect.
Still, all the parsing and first-phase lookup has already been done,
without preventing inlining
in any way. You are correct in the sense that there may be less
benefit than some expect, since
overload resolution etc. will still happen at the point of use.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAFk2RUb8%3Deab9pMcmOp6aT2B2O6RUn0qs4GzYSGG1-n0PfhK2Q%40mail.gmail.com.
Nicol Bolas
2017-05-10 15:06:45 UTC
Permalink
Post by Mingxin Wang
This is an update version for another topic “Adding the Keyword
“interface” in C++
<https://groups.google.com/a/isocpp.org/forum/#!topic/std-proposals/uyfpeyEyW4o>
”
First, there's no need to create a new thread because you changed the name
of the keyword. There was still an active discussion going on in the old
thread.

Second, the keyword itself is essentially irrelevant. Your feature is not
about adding a keyword to C++; it's about adding automatic type erasure
generation based on a "concept" of some form. What keyword you use to
invoke this does not matter. So you shouldn't title the thread "Adding
keyword X to C++"; it should be titled based on what the actual feature is.

Third, as for the feature, it's still weak. It would be much better to base
it on a concept. It's a much better mechanism for specifying this sort of
thing.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/0f7b92a7-2318-4786-b617-b1fcc6cf0c2c%40isocpp.org.
Mingxin Wang
2017-05-12 16:41:37 UTC
Permalink
To clarify the motivation and scope, Mr. Bengt Gustafsson and I have
discussed about this feature via email yesterday. Our opinions and
understanding are summarized as follows.

*What do we have now*

Before the “proxies”, the most widely adopted method to implement
polymorphism in C++ is to use virtual functions, which exist from the very
beginning, and there is little update on this feature so far.

We used to define base classes with pure virtual functions, define derived
classes inheriting the base, and instantiate derived classes with dynamic
memory allocation, as is shown below:

class Base {
public:
virtual void f() = 0;
virtual int g(double) = 0;
virtual ~Base() {}

protected:
Base() = default;
};

class Derived : public Base {
public:
Derived() = default;
void f() override;
int g(double) override;
};

Base* base = new Derived();
base->f();
base->g(3);
delete base;

*What is the “proxies”*

It is obvious that dynamic memory allocation is harmful to performance, but
why are we using it in the code above? Because, as we are using
polymorphism with a uniform base class, the implementation for the derived
class is completely unknown, including the memory required to construct the
derived class (*sizeof(Derived)*).

I have been trying to find out whether there is a better solution, and I
found it helpful to decouple the interfaces (the base classes) and the
implementations (the derived classes) with the “proxies”, which is an
update version for virtual functions, as is shown below.

<Loading Image...>

The code above can be reconstructed with the “proxies”, as is shown below:

proxy P {

void f();

int g(double);

};



class T {

public:

void f();

int g(double);

};



T t;

P p(t);

p.f();
p.g(3);

Everything is so natural! The implementation only need to care about its
own semantics ignoring how it corresponds with the requirements (no
overriding any more), and no dynamic memory allocation happens at all!

Because the “proxies” is an update version for virtual functions, *it can
proxy any expression that can be declared virtual, including the overloads
and operators, but is not able to proxy the expressions that cannot be
declared virtual*, e.g. inner types, constructors. Thus, it is difficult to
convert every concept into a proxy, but on the contrary, it is easy to
generate a concept from any proxy.

*How should the compiler generate the code for a proxy?*

As Mr. Bengt Gustafsson wrote in the mail:

It would be good if your example was a little bit more elaborate than
Runnable, for instance having a couple of named methods. As it stands today
it seems implementable as library-only as the operator()() which is the
"key feature" here drowns in all the boilerplate methods. You could also
add comments to make it obvious which sections contain the methods
generated from the proxy declaration. Also you should not write "here is a
possible implementation" as it suggests that this is code you should write
yourself (defeating the whole purpose of the proposal) but "here is the
equivalent of the code that the compiler would automatically generate for
the proxy MyProxy".
Providing we have a proxy “Foo” declared as:

proxy Foo {
void operator()();
double f();
void g(int);
};

Here is the equivalent of the code that the compiler would automatically
generate for the proxy:

class Foo {
public:
/* A template constructor */
template <class Data>
// Data is the concrete type
Foo(Data& data) requires
// The parameter shall be passed as reference
requires(Data data, int arg_0) {
// The auto-generated concept corresponding to the proxy
{ data() };
{ data.f() } -> double;
{ data.g(std::move(arg_0)) };
} {
Implementation<Data> a(data);
// A temporary value
memcpy(data_, &a, sizeof(Abstraction));
// Copy the implementation data to the char array byte by byte
}

/* Auto-generated constructors and operators */
Foo() = default;
Foo(Foo&&) = default;
Foo(const Foo&) = default;
Foo& operator=(Foo&&) = default;
Foo& operator=(const Foo&) = default;

/* Calling the corresponding member functions with the auto-generated
entries */
void operator()() { reinterpret_cast<Abstraction*>(data_)->op_0(); }
double f() { return reinterpret_cast<Abstraction*>(data_)->op_1(); }
void g(int arg_0) {
reinterpret_cast<Abstraction*>(data_)->op_2(std::move(arg_0)); }

private:
class Abstraction {
public:
/* Stores a type-erased pointer */
Abstraction(void* data) : data_(data) {}

/* The pure virtual functions correspond to each expression declared in
the proxy respectively */
virtual void op_0() = 0;
virtual double op_1() = 0;
virtual void op_2(int&&) = 0;
// Every parameter is passed as rvalue

/* A helper function */
template <class T>
T* get() { return static_cast<T*>(data_); }

private:
void* data_;
};

template <class Data>
class Implementation final : public Abstraction {
public:
/* Initialize the base class */
Implementation(Data& data) : Abstraction(&data) {}

/* Implement the virtual functions with concrete function calls */
void op_0() override { (*get<Data>())(); }
double op_1() override { return get<Data>()->f(); }
void op_2(int&& arg_0) override {
get<Data>()->g(std::forward<int>(arg_0)); } // The parameter shall be
forward to the concrete member functions
};

char data_[sizeof(Abstraction)];
// Declares a field large enough for any Implementation<T>
};

*In what way can and should predefined Concepts be reused to generate
abstract classes that forward functionality to concrete types? (possibly
not at all?)*

I insist that abstract classes shall not be generated from concepts,
because concepts can do a lot more than virtual functions, but it is
possible to generate a concept from any proxy for compile-time type safety.

As Mr. Bengt Gustafsson wrote in the mail:

Regarding the reuse of Concepts for this purpose I find it very elegant to
do so. I would not be so afraid of the load on the compiler to disentangle
the concept hierarchy (it has to do this anyway to be able to see if a T
fulfills a concept). However, I have a hard time understanding how the
non-member requirements are to be translated to the corresponding proxy.
For instance if we require that the type T fulfilling a concept Shiftable
has a operator<<(ostream&, T), this would require the proxy to contain a
virtual ostream& __leftshift(ostream&);



and then have the compiler emit a call to it when it sees

proxy<Shiftable> p;

cout << p;

I guess this is doable, but it does complicate the implementation. What if
there are two proxy parameters to a function (for different proxies) and
then you try to multiply those values. Doesn't this create a need for
multi-dispatch?
Furthermore, I'm pretty sure that a concept can define that a complying
type should have a certain method without specifying the return type of the
requres requires(Data& d) { d.myFunc(); }
When we then try to create the proxy the return type of the created virtual
function myFunc() is unknown!
I don't want to rule out concepts as the basis for proxies but I think it
needs a lot more thinking and that not all concepts will be possible to
proxy, or maybe that the rules for how to write concepts need to be revised
again to make proxying all concepts possible, but maybe concepts hasÂŽve
gone too far now to be possible. Personally I don't care too much for
concepts as they are going to be too cumbersome to use and will probably
find little use outside the standard library. Especially with the contorted
syntax of the concepts TS it was really awful but I have seen that they are
cleaning up some of the mess more recently. I haven't followed these
discussions too closely though.
Also I think it is possible to write requirement like: requires { typename
Data::value_type; } which just says that Data should have a nested type
value_type without telling what it is. This is perfectly ok in the template
world but fails utterly in the proxy realm.
*Ensure proper ownership management*

As Mr. Bengt Gustafsson wrote in the mail:

When it comes to lifetime handling I think that Jakob Riedle is right in
that we should strive for getting the same type of semantics as for regular
types, as he showed in his code box. I think this means that there must be
some more magic in the proxy code as it will optionally own the data
itself. The sad part is that now it becomes hard to see how shared_ptr<T>
and shared_ptr<MyProxy> could co-exist pointing to the same T object (T
fulfilling MyProxy of course). But this may be a necessary sacrifice as we
can't support all user written shared pointers out there anyway. Or maybe
it can be supported (similarly to how a shared_ptr<BASE> can be assigned
from a shared_ptr<DERIVED> and they still share the refcount. This will
require some surgery in the shared_ptr class I suspect. Thinking about the
details of this will reveal new insights, no doubt.
This is a question I've been thinking about over and over again. On the one
hand, if the lifetime issue is coupled with the “proxies”, it violates the
single responsibility principle, and it becomes unfriendly to “stack
objects”, whose lifetime is controlled by the execution of program, but it
seems to be easier to use. On the other hand, if the lifetime issue is
decoupled from the “proxies”, users are responsible for managing the
lifetime, however, they are free to specify the algorithms (for “heap
objects”, from new/delete to GC).

After some research, I found it unnecessary to couple the lifetime issue
with the “proxies” at all, because most situations that require
polymorphism are implementable without dynamic memory allocation, as the
example mentioned earlier demonstrates.

If a proxy is used asynchronously, it is required to construct the object
on the heap. We can use “Async Concurrent Invoke” (defined in “Structural
Support for C++ Concurrency”, P0642, available here
<https://groups.google.com/a/isocpp.org/forum/#!topic/std-proposals/tQb9t6Hnu6M>)
and delete the object in the callback, as is shown below:

Object* object = new Object(...); // The data to be used asynchronously
P p(*object); // Declaring a proxy
con::async_concurrent_invoke([=] { delete object; }, /* some concurrent
callers */);

*Should "Static" concepts behave like "dynamic" ones only in that they
operate at compile time? Why is a different approach preferable?*

As Mr. Bengt Gustafsson wrote in the mail:

Finally, if concepts are to be involved as the way to specify contents of
proxies we could maybe solve the syntactical problems using a "magic"
library template which could be called dynamic, box, proxy or something. In
general I don't like mixing up library with language, but as it seems very
std::box<Callable<void()>> myFunction; // Works essentially as
std::function
std::box<MyConcept> x;
void Func(MyConcept& p); // This is implicitly a
template function as MyConcept is a concept (using terse template syntax)
void Func(std::box<MyConcept>& p); // This is a regular function
taking a magic box wrapping any object complying to MyConcept.
However, I don't think box is the best name as it doesn't really do
_exactly_ what boxing in other languages does.
As I suppose that abstract classes shall not be generated from concepts, I
do not vote for this idea. Besides, because this feature requires code
generation at compile-time, I tend to define the “proxies” as a “type of
type” rather than a “template <typename...> typename”.

Mingxin Wang
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/f6c3b3d1-f723-41de-ac23-85defc434abe%40isocpp.org.
Nicol Bolas
2017-05-12 21:28:02 UTC
Permalink
Things your design is potentially lacking (which aren't covered in the meat
of my reply):

Proxies need an equivalent of `dynamic_cast`. Or rather, an equivalent of
`any_cast`, which is effectively the same thing. You need to be able to
extract the type back out of the proxy. And it must be the exact type you
provided the proxy.

Also, I think we need proxy maps. Remember the old C++11 concepts with
concept maps, which were basically ways to coerce a type to fit a concept
it doesn't really fit? I think this would be *very* useful for proxies.

A proxy map is a type which implements a particular proxy for a particular
given type(s), as defined as a template. Consider a callable proxy. That
works well enough for class types, but what if you want your callable to be
able to use function pointers or member pointers? You need some kind of
class type to do the translation there.

That would be the point of the proxy map. For a given proxy and for a given
type (or range of types), you create a class. When a user tries to turn one
of those types into that class, it results in the construction of a proxy
map type with that value, which is then passed into the proxy type.

Unless you can come up with a more elegant way to handle non-class types,
or types that don't quite fit a proxy.

Basically, my litmus test for proxy design is this: the design is deficient
if you cannot implement `std::function` by using a proxy.
Post by Mingxin Wang
To clarify the motivation and scope, Mr. Bengt Gustafsson and I have
discussed about this feature via email yesterday. Our opinions and
understanding are summarized as follows.
*What do we have now*
Before the “proxies”, the most widely adopted method to implement
polymorphism in C++ is to use virtual functions, which exist from the very
beginning, and there is little update on this feature so far.
We used to define base classes with pure virtual functions, define derived
classes inheriting the base, and instantiate derived classes with dynamic
class Base {
virtual void f() = 0;
virtual int g(double) = 0;
virtual ~Base() {}
Base() = default;
};
class Derived : public Base {
Derived() = default;
void f() override;
int g(double) override;
};
Base* base = new Derived();
base->f();
base->g(3);
delete base;
*What is the “proxies”*
It is obvious that dynamic memory allocation is harmful to performance,
but why are we using it in the code above? Because, as we are using
polymorphism with a uniform base class, the implementation for the derived
class is completely unknown, including the memory required to construct the
derived class (*sizeof(Derived)*).
Nobody forced you to allocate memory. You could just as easily have done:

Derived d{};
Base *base = &d;
//etc.

I have been trying to find out whether there is a better solution, and I
Post by Mingxin Wang
found it helpful to decouple the interfaces (the base classes) and the
implementations (the derived classes) with the “proxies”, which is an
update version for virtual functions, as is shown below.
<https://lh3.googleusercontent.com/-3m2W7ogUxqE/WRXiIE3hI2I/AAAAAAAAAEY/A72ROREd4pg_fSbuhJ38_D80824FlOTbQCLcB/s1600/11.png>
proxy P {
void f();
int g(double);
};
class T {
void f();
int g(double);
};
T t;
P p(t);
p.f();
p.g(3);
Everything is so natural! The implementation only need to care about its
own semantics ignoring how it corresponds with the requirements (no
overriding any more), and no dynamic memory allocation happens at all!
The only difference between that and what I did up there is that there's no
need to dereference `t` when storing it in a `p`. If this is intended to be
a motivation for proxies, it doesn't really work.

A much better motivation for them is, well, pretty much any case for
type-erasure (since despite your attempt to call it something else, this is
clearly language-based type-erasure). One of the main reasons for
type-erasure is to provide polymorphism *without* being invasive. So it's
about working with types that don't have base classes, which you personally
do not own and therefore cannot give them base classes.

And equally importantly, types where you don't *want* to give them base
classes. Like `std::string`.

Because the “proxies” is an update version for virtual functions, *it can
Post by Mingxin Wang
proxy any expression that can be declared virtual, including the overloads
and operators, but is not able to proxy the expressions that cannot be
declared virtual*, e.g. inner types, constructors.
No.

We're talking about a *language feature*. You should not limit the behavior
of a language feature simply because the analogy no longer fits. If a
"proxy" *genuinely* cannot be implemented which can forward certain
constructs, so be it. But you shouldn't limit the design just to things
that can be declared with virtual functions simply because it doesn't fit
into the analogy you built for it.

That doesn't mean we have to do it through concepts, of course. But the
idea that you *can't* have a proxy include non-member functions just
because you can't have non-member virtual functions is needlessly limited.

*Ensure proper ownership management*
Post by Mingxin Wang
When it comes to lifetime handling I think that Jakob Riedle is right in
that we should strive for getting the same type of semantics as for regular
types, as he showed in his code box. I think this means that there must be
some more magic in the proxy code as it will optionally own the data
itself. The sad part is that now it becomes hard to see how shared_ptr<T>
and shared_ptr<MyProxy> could co-exist pointing to the same T object (T
fulfilling MyProxy of course). But this may be a necessary sacrifice as we
can't support all user written shared pointers out there anyway. Or maybe
it can be supported (similarly to how a shared_ptr<BASE> can be assigned
from a shared_ptr<DERIVED> and they still share the refcount. This will
require some surgery in the shared_ptr class I suspect. Thinking about the
details of this will reveal new insights, no doubt.
This is a question I've been thinking about over and over again. On the
one hand, if the lifetime issue is coupled with the “proxies”, it violates
the single responsibility principle, and it becomes unfriendly to “stack
objects”, whose lifetime is controlled by the execution of program, but it
seems to be easier to use. On the other hand, if the lifetime issue is
decoupled from the “proxies”, users are responsible for managing the
lifetime, however, they are free to specify the algorithms (for “heap
objects”, from new/delete to GC).
After some research, I found it unnecessary to couple the lifetime issue
with the “proxies” at all, because most situations that require
polymorphism are implementable without dynamic memory allocation, as the
example mentioned earlier demonstrates.
Really? Because `std::function` disagrees with you. `std::function` is
functionally a proxy for a type that is callable with the `operator()`
overload specified by the `function`'s template parameter. And yet, it also
requires copyable value semantics.

So clearly there is a genuine need for type-erasure+value semantics. Your
personal use cases may not need it, but if `function` (and `any` for that
matter) are any indication, combining the two is something people really do
use.

The way to specify the reference/value semantics for a proxy is pretty
easy: use the tools we already have, where possible.

//Value semantics by default
proxy val_proxy
{
};

//Value semantics, move-only.
proxy move_proxy
{
move_proxy (const move_proxy &) = delete;
move_proxy (move_proxy&&) = default;
move_proxy &operator=(const move_proxy &) = delete;
move_proxy &operator=(move_proxy&&) = default;
};

//Has reference semantics
proxy ref_proxy reference
{
};

For value proxies, we should allow implementations to provide small object
optimization, just as we permit for `any` and `function`.

Semantics specification *should not* include smart pointers. If a user
wants to wrap a smart pointer in such a proxy, then its up to them to do it
properly. For example, if you have a proxy of some sort, and you have a
`shared_ptr<T>` where `T` fulfills the proxy requirements, then the way to
wrap it is simple: write a type that forwards those requirements and stores
a `shared_ptr<T>`:

class shared_proxy_T
{
public:
shared_proxy_T(shared_ptr<T>); //Fill in `t_`.

//Add proxy requirements, forwarded to `t_`;

private:
shared_ptr<T> t_;
};

The proxy type used here would need to have value semantics, either copy or
move (or value semantics, but . If you wanted to proxy a `unique_ptr<T>`,
then you do the same thing, but it

Now, we might have a feature where you could have the compiler generate a
wrapper given a pointer-like type and a proxy (which specifies what
operations to forward). Indeed this could perhaps be done via the proxy map
feature I suggested, which would allow it to work for *any* `shared_ptr<T>`
where `T` fits the proxy.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/54105606-cdde-4acf-8232-74c75c3140e3%40isocpp.org.
Mingxin Wang
2017-05-13 10:21:05 UTC
Permalink
Post by Nicol Bolas
Things your design is potentially lacking (which aren't covered in the
Proxies need an equivalent of `dynamic_cast`. Or rather, an equivalent of
`any_cast`, which is effectively the same thing. You need to be able to
extract the type back out of the proxy. And it must be the exact type you
provided the proxy.
Also, I think we need proxy maps. Remember the old C++11 concepts with
concept maps, which were basically ways to coerce a type to fit a concept
it doesn't really fit? I think this would be *very* useful for proxies.
A proxy map is a type which implements a particular proxy for a particular
given type(s), as defined as a template. Consider a callable proxy. That
works well enough for class types, but what if you want your callable to be
able to use function pointers or member pointers? You need some kind of
class type to do the translation there.
A function can be regarded as a const reference to a pointer, so it is OK
to write the code as shown below:

proxy Runnable {
void operator()();
};

void fun();

Runnable r(fun);
r();
Post by Nicol Bolas
That would be the point of the proxy map. For a given proxy and for a
given type (or range of types), you create a class. When a user tries to
turn one of those types into that class, it results in the construction of
a proxy map type with that value, which is then passed into the proxy type.
Unless you can come up with a more elegant way to handle non-class types,
or types that don't quite fit a proxy.
Basically, my litmus test for proxy design is this: the design is
deficient if you cannot implement `std::function` by using a proxy.
I think std::function can be replaced by the proxy in many situations with
less type requirements (types are not required to be CopyConstructible) and
lower runtime overhead (as I roughly tested, *std::function<void()> is
approximately 3 times slower to construct than the proxy Runnable* with
ordinary function pointers on my PC).

Consider we are to build a threadpool with a queue of tasks (as a buffer),
and we simply wrap each task into a std::function<void()> (e.g.
std::queue<std::function<void()>>), we are unable to submit only
MoveConstructible types (e.g. std::packaged_task<void()>) directly.
Although it is convenient for users to handle the lifetime issue with
std::function<void>, it is much elegant to handle the issue with the
support of "Concurrent Invoke" ("Structural Support for C++ Concurrency",
P0642) because there is only needed to be one concrete implementation for
each proxy in this situation, and the lifetime can be handled with the flow
of control, as is shown below:

/* some tasks are defined here*/
auto task_a = [] { puts("Lambda"); };
void task_b();
auto task_c = [&] { /* do something with "stack objects" */ };

/* Sync Concurrent Invoke */
con::sync_concurrent_invoke([] { puts("Calling thread does nothing but
wait"); },
/* some concurrent callers that may submit
tasks as proxies to some threadpools */);
// Sync Concurrent Invoke will block until all the tasks are done.

The reason why std::function requires the concrete implementation to be
CopyConstructible is perhaps that polymorphism is usually accompanied by
the use of containers (otherwise, we can use template), and containers
often requires the specified types to be CopyConstructible, thus
std::function requires the types it wraps to be CopyConstructible.

Because the “proxies” is an update version for virtual functions, *it can
Post by Nicol Bolas
Post by Mingxin Wang
proxy any expression that can be declared virtual, including the overloads
and operators, but is not able to proxy the expressions that cannot be
declared virtual*, e.g. inner types, constructors.
No.
We're talking about a *language feature*. You should not limit the
behavior of a language feature simply because the analogy no longer fits.
If a "proxy" *genuinely* cannot be implemented which can forward certain
constructs, so be it. But you shouldn't limit the design just to things
that can be declared with virtual functions simply because it doesn't fit
into the analogy you built for it.
That doesn't mean we have to do it through concepts, of course. But the
idea that you *can't* have a proxy include non-member functions just
because you can't have non-member virtual functions is needlessly limited.
I wrote this only to limit the expressions that shall appear in a proxy
definition. Particularly, an expression in the implementation is not
necessary to be a member function, it can even be static. It is defined
that a proxy can and can only proxy the expressions that are able to be
declared virtual, because C++ has done a lot in the virtual function
mechanism, and there is enough research in what expressions are suitable
for runtime polymorphism.
Post by Nicol Bolas
*Ensure proper ownership management*
Post by Mingxin Wang
When it comes to lifetime handling I think that Jakob Riedle is right in
that we should strive for getting the same type of semantics as for regular
types, as he showed in his code box. I think this means that there must be
some more magic in the proxy code as it will optionally own the data
itself. The sad part is that now it becomes hard to see how shared_ptr<T>
and shared_ptr<MyProxy> could co-exist pointing to the same T object (T
fulfilling MyProxy of course). But this may be a necessary sacrifice as we
can't support all user written shared pointers out there anyway. Or maybe
it can be supported (similarly to how a shared_ptr<BASE> can be assigned
from a shared_ptr<DERIVED> and they still share the refcount. This will
require some surgery in the shared_ptr class I suspect. Thinking about the
details of this will reveal new insights, no doubt.
This is a question I've been thinking about over and over again. On the
one hand, if the lifetime issue is coupled with the “proxies”, it violates
the single responsibility principle, and it becomes unfriendly to “stack
objects”, whose lifetime is controlled by the execution of program, but it
seems to be easier to use. On the other hand, if the lifetime issue is
decoupled from the “proxies”, users are responsible for managing the
lifetime, however, they are free to specify the algorithms (for “heap
objects”, from new/delete to GC).
After some research, I found it unnecessary to couple the lifetime issue
with the “proxies” at all, because most situations that require
polymorphism are implementable without dynamic memory allocation, as the
example mentioned earlier demonstrates.
Really? Because `std::function` disagrees with you. `std::function` is
functionally a proxy for a type that is callable with the `operator()`
overload specified by the `function`'s template parameter. And yet, it also
requires copyable value semantics.
So clearly there is a genuine need for type-erasure+value semantics. Your
personal use cases may not need it, but if `function` (and `any` for that
matter) are any indication, combining the two is something people really do
use.
As you think copying the wrapped object is sometimes necessary, could you
provide a meaningful use case which is inconvenient to implement with the
proxy or may reduce runtime overhead while implemented with std::function?
Post by Nicol Bolas
The way to specify the reference/value semantics for a proxy is pretty
easy: use the tools we already have, where possible.
//Value semantics by default
proxy val_proxy
{
};
//Value semantics, move-only.
proxy move_proxy
{
move_proxy (const move_proxy &) = delete;
move_proxy (move_proxy&&) = default;
move_proxy &operator=(const move_proxy &) = delete;
move_proxy &operator=(move_proxy&&) = default;
};
//Has reference semantics
proxy ref_proxy reference
{
};
For value proxies, we should allow implementations to provide small object
optimization, just as we permit for `any` and `function`.
Semantics specification *should not* include smart pointers. If a user
wants to wrap a smart pointer in such a proxy, then its up to them to do it
properly. For example, if you have a proxy of some sort, and you have a
`shared_ptr<T>` where `T` fulfills the proxy requirements, then the way to
wrap it is simple: write a type that forwards those requirements and stores
class shared_proxy_T
{
shared_proxy_T(shared_ptr<T>); //Fill in `t_`.
//Add proxy requirements, forwarded to `t_`;
shared_ptr<T> t_;
};
The proxy type used here would need to have value semantics, either copy
or move (or value semantics, but . If you wanted to proxy a
`unique_ptr<T>`, then you do the same thing, but it
Now, we might have a feature where you could have the compiler generate a
wrapper given a pointer-like type and a proxy (which specifies what
operations to forward). Indeed this could perhaps be done via the proxy map
feature I suggested, which would allow it to work for *any*
`shared_ptr<T>` where `T` fits the proxy.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/89115626-5f2e-4c42-bd8e-f2c3341c7d4a%40isocpp.org.
Bengt Gustafsson
2017-05-13 13:29:14 UTC
Permalink
Post by Mingxin Wang
As you think copying the wrapped object is sometimes necessary, could you
Post by Mingxin Wang
provide a meaningful use case which is inconvenient to implement with the
proxy or may reduce runtime overhead while implemented with std::function?
This would happen often, for instance in a std::vector<MyProxy> which, as
the proxy is by value should own its elements regardless of their actual
types. I think this shows the gist of the ownership problem, because if you
have a std::vector<MyProxy*> then each vector element should be able to
point to any object fulfilling MyProxy. But in this case, where is the
actual proxy object performing the method translation?

I think that the list that Jakob Riedle wrote, and which I copied below
shows the basic idea to start out from. It must be noted that a reference
to a proxy as in "Callable<void()>& c2 = f1;" is not a normal reference,
for what would it refer to? Instead it is a "by reference proxy" which the
compiler understands how to create, thereby allowing different types of
ownership semantics. This only goes so far though, it doesn't solve the
std::vector<MyProxy*> case. Possibly a std::vector<MyProxy&> could be
allowed but this seems odd as containers of references are not allowed in
general.

std::function<void()> f1 = /**/;
Post by Mingxin Wang
std::function<void()> f2();
const std::function<void()> f3 = /**/;
Callable<void()> c1 = f1; // This copies f1. Internally, an lvalue object
of type std::function<void()> is held.
Callable<void()>& c2 = f1; // This references f1. Internally, an lvalue
reference (std::function<void()>&) is stored.
Callable<void()>&& c3 = f2(); // Internally stored as '
std::function<void()>&&'.
Callable<void()> c4 = f2(); // This moves the result of f2 into c4.
Internally stored as real std::function<void()> lvalue .
const Callable<void()>& c4 = f3; //Internally 'const &' as handle.
// and so on...
Another thing resulting from this is that this function has a by value
parameter of type 'reference proxy to MyProxy':

void myFunc(MyProxy& s);

This by value object contains a pointer to the actual parameter and a
vtable pointer (or similar) to allow myFunc to call the methods of MyProxy
regardless of the actual parameter type.

This reuse of the & operator clashes with the possibility to create a
reference to a proxy, so if myFunc needs to forward s to a sub-function a
deep copy has to be made (which is just two pointers, but anyway).

Thus it looks as reusing & to indicate proxy ownership is not appropriate
after all, despite the elegance. We need to be able to specify both a
reference to a proxy and a proxy which refers to the actual object.

I would like this to work, but then we must disentangle how concept
contents maps to proxy contents:

concept MyConcept { .... };

class MyClass { ... }; // Class that fulfills MyConcept

MyClass obj;
MyClass& ref = obj;

std::val_proxy<MyConcept> vp = obj; // Deep copies obj into some internal
or heap storage.
std::ref_proxy<MyConcept> rp = obj; // Refers to obj.

More importanty:

myRefFun(std::ref_proxy<MyConcept> ra);

myRefFun(obj); // Refers to obj
myRefFun(rp); // deep-copies the ref_proxy<MyConcept> rp as expected
myRefFun(vp); // Creates a ref_proxy from the val_proxy by refering to
the internal copy of obj

std::vector<std::ref_proxy<MyConcept>> myVec; // This can now be done,
and you can push back references to any complying objects.

The std::ref_proxy and std::val_proxy templates must be magic on the same
level as initializer_list which seems to be a library class template but is
magic. This ducks the issues of having to add keywords or new/reused
operators. It still does not solve the problem of shared ownership between
real and proxy pointers as elegantly as between sub- and baseclass shared
pointers, which brings me back to the original complaint of mine that
ownership and proxying issues should be separated.

What I think we need is a lower level feature which can be used by
std::ref_proxy and std::val_proxy as well as any number of other similar
templates such as shared_proxy and unique_proxy. Something like this:

template<concept C> class ref_proxy {
public:
template<C S> proxy(S& src) : mObject(&src), c(src) {}

C(mObject);

private:
C* mObject;
C c;
};

This introduces concepts as template parameters in order to create a
"higher order template". The first magic happens on the C(mObject) line
which indicates that the mObject pointer is to be used as 'this' in
implementations of all methods requires by C. The second magic is the C*
which is not a normal pointer but a
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/7bf7645d-004d-474f-b260-32712bd2e490%40isocpp.org.
Bengt Gustafsson
2017-05-13 13:42:22 UTC
Permalink
Sorry, that got sent a bit too early... let me complete the last part as
best I can:


What I think we need is a lower level feature which can be used by
std::ref_proxy and std::val_proxy as well as any number of other similar
templates such as shared_proxy and unique_proxy. Something like this:

template<concept C> class ref_proxy {
public:
template<C S> proxy(S& src) : mObject(&src), c(src) {}

c(mObject);

private:
void* mObject;
C c;
};

This introduces concepts as template parameters in order to create a
"higher order template". The first magic happens on the c(mObject) line
which indicates that the mObject pointer is to be used as 'this' in
implementations of all methods requires by C. The second magic is the C c;
and its initialization from src. What this means is that a by value concept
object in a template<concept> class contains the vtable pointer and
initiates it in the template ctor.

I think it is pretty obvious that val_proxy (with or without SOO) as well
as shared_proxy etc. can be implemented similarly.

This idea was just a result of trying to de-construct the proposed "proxy"
idea to find the most basic "magic feature" possible. Just view this as a
starting point for a discussion if and how concepts as template parameters
can be used to implement proxies, but also if there are other uses for this
feature, how this feature actually works and how the c(mObject) line would
be more appropriately expressed syntactically.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/ea97f8c7-8d2e-4bcf-b694-5198da00c449%40isocpp.org.
Mingxin Wang
2017-05-14 02:24:29 UTC
Permalink
Dear Mr. Bengt Gustafsson and Mr. Nicol Bolas,

Thank you for your ideas.

Such requirements really do exist! But I think this is more appropriate to
be a library feature rather than a lanuage feature to *bind* a value to a
proxy. I have just built a "Wrapper" class, as is shown below:

template <class P /*, class Allocator = std::allocator<void>*/>
// Maybe some allocator is allowed here
class Wrapper {
public:
template <class Data>
Wrapper(Data data) :
data_(new Implementation<Data>(std::move(data))),
p_(static_cast<Implementation<Data>*>(data_.get())->get()) {}

Wrapper(const Wrapper& rhs) :
data_(rhs.data_.get() == nullptr ? nullptr : rhs.data_->clone()),
// The new data is a copy from the original data with polymorphism
p_(rhs.p_) {}

Wrapper() = default;
Wrapper(Wrapper&&) = default;

/* Access to the proxy */
P& get() { return p_; }
operator P& () { return p_; }

private:
class Abstraction {
public:
/* Pure virtual methods to construct and destruct */
virtual Abstraction* clone() = 0;
virtual ~Abstraction() {}
};

template <class Data>
class Implementation : public Abstraction {
public:
Implementation(Data&& data) : data_(std::forward<Data>(data)) {}
Implementation(const Data& data) : data_(data) {}

Data& get() { return data_; }

Abstraction* clone() override { return new Implementation(data_); }

private:
Data data_;
};

/* The two data structures hold distinct pointers */
std::unique_ptr<Abstraction> data_;
P p_;
};

*With this class, it is possible to use "Wrapper<SomeProxy>" to turn a
proxy into a value wrapper.* But as it is a wrapper (like
"std::reference_wrapper" does), certain member functions are provided to
access the proxy ("get()" and "operator P&").

I think this is enough so far.

P.S. It is possible for a proxy to proxy any type, including member
functions, as long as they are not temporary variables:
auto f = std::bind(...);
Runnable r(f);

Mingxin Wang
Post by Mingxin Wang
As you think copying the wrapped object is sometimes necessary, could you
Post by Mingxin Wang
Post by Mingxin Wang
provide a meaningful use case which is inconvenient to implement with the
proxy or may reduce runtime overhead while implemented with std::function?
This would happen often, for instance in a std::vector<MyProxy> which, as
the proxy is by value should own its elements regardless of their actual
types. I think this shows the gist of the ownership problem, because if you
have a std::vector<MyProxy*> then each vector element should be able to
point to any object fulfilling MyProxy. But in this case, where is the
actual proxy object performing the method translation?
I think that the list that Jakob Riedle wrote, and which I copied below
shows the basic idea to start out from. It must be noted that a reference
to a proxy as in "Callable<void()>& c2 = f1;" is not a normal reference,
for what would it refer to? Instead it is a "by reference proxy" which the
compiler understands how to create, thereby allowing different types of
ownership semantics. This only goes so far though, it doesn't solve the
std::vector<MyProxy*> case. Possibly a std::vector<MyProxy&> could be
allowed but this seems odd as containers of references are not allowed in
general.
std::function<void()> f1 = /**/;
Post by Mingxin Wang
std::function<void()> f2();
const std::function<void()> f3 = /**/;
Callable<void()> c1 = f1; // This copies f1. Internally, an lvalue
object of type std::function<void()> is held.
Callable<void()>& c2 = f1; // This references f1. Internally, an lvalue
reference (std::function<void()>&) is stored.
Callable<void()>&& c3 = f2(); // Internally stored as '
std::function<void()>&&'.
Callable<void()> c4 = f2(); // This moves the result of f2 into c4.
Internally stored as real std::function<void()> lvalue .
const Callable<void()>& c4 = f3; //Internally 'const &' as handle.
// and so on...
Another thing resulting from this is that this function has a by value
void myFunc(MyProxy& s);
This by value object contains a pointer to the actual parameter and a
vtable pointer (or similar) to allow myFunc to call the methods of MyProxy
regardless of the actual parameter type.
This reuse of the & operator clashes with the possibility to create a
reference to a proxy, so if myFunc needs to forward s to a sub-function a
deep copy has to be made (which is just two pointers, but anyway).
Thus it looks as reusing & to indicate proxy ownership is not appropriate
after all, despite the elegance. We need to be able to specify both a
reference to a proxy and a proxy which refers to the actual object.
I would like this to work, but then we must disentangle how concept
concept MyConcept { .... };
class MyClass { ... }; // Class that fulfills MyConcept
MyClass obj;
MyClass& ref = obj;
std::val_proxy<MyConcept> vp = obj; // Deep copies obj into some
internal or heap storage.
std::ref_proxy<MyConcept> rp = obj; // Refers to obj.
myRefFun(std::ref_proxy<MyConcept> ra);
myRefFun(obj); // Refers to obj
myRefFun(rp); // deep-copies the ref_proxy<MyConcept> rp as expected
myRefFun(vp); // Creates a ref_proxy from the val_proxy by refering to
the internal copy of obj
std::vector<std::ref_proxy<MyConcept>> myVec; // This can now be done,
and you can push back references to any complying objects.
The std::ref_proxy and std::val_proxy templates must be magic on the same
level as initializer_list which seems to be a library class template but is
magic. This ducks the issues of having to add keywords or new/reused
operators. It still does not solve the problem of shared ownership between
real and proxy pointers as elegantly as between sub- and baseclass shared
pointers, which brings me back to the original complaint of mine that
ownership and proxying issues should be separated.
What I think we need is a lower level feature which can be used by
std::ref_proxy and std::val_proxy as well as any number of other similar
template<concept C> class ref_proxy {
template<C S> proxy(S& src) : mObject(&src), c(src) {}
C(mObject);
C* mObject;
C c;
};
This introduces concepts as template parameters in order to create a
"higher order template". The first magic happens on the C(mObject) line
which indicates that the mObject pointer is to be used as 'this' in
implementations of all methods requires by C. The second magic is the C*
which is not a normal pointer but a
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/0e6a347a-c299-4241-b92c-e952c32c254e%40isocpp.org.
Nicol Bolas
2017-05-14 02:56:16 UTC
Permalink
Post by Mingxin Wang
Dear Mr. Bengt Gustafsson and Mr. Nicol Bolas,
Thank you for your ideas.
Such requirements really do exist! But I think this is more appropriate to
be a library feature rather than a lanuage feature to *bind* a value to a
[...]
*With this class, it is possible to use "Wrapper<SomeProxy>" to turn a
Post by Mingxin Wang
proxy into a value wrapper.* But as it is a wrapper (like
"std::reference_wrapper" does), certain member functions are provided to
access the proxy ("get()" and "operator P&").
I think this is enough so far.
The deficiencies relative to a hand-written type-erased type are readily
apparent:

1) The wrapper takes up more memory.
2) The wrapper is less efficient (since it has two type-erasures instead of
just one).
3) The wrapper is *far* less convenient to actually *use*, since it's not
the actual proxy; you have to use `get` or an implicit conversion to get at
the behavior you want.

If it would be better for a user to hand-write a type-erased type rather
than use your language-based one, then your language feature has design
problems.

Is not the whole point of this feature to make type-erasure a first-class
part of the language? If so, then it needs to be usable by *everyone* who
wants to use type-erasure, not just everyone who wants reference semantics.
Concepts doesn't have this flaw; if you want to pass a value that satisfies
a concept around by value, you can (so long as the concept allows such
copying/moving).

Why can't proxies?

P.S. It is possible for a proxy to proxy any type, including member
Post by Mingxin Wang
auto f = std::bind(...);
Runnable r(f);
But now you have to make sure that `f` stays alive so long as `r` does.
With regular function pointers, you don't have to care; function pointers
are valid forever. As are member pointers, but those don't work here.

If you were to write a hand-written `Runnable<Func>` type, even if it only
has reference semantics, you would still give it native support for member
pointers. Because those are genuine "runnables" and should have first-class
access like function pointers.

So again, I find myself saying that if hand-written type-erased types are
better than your language-based ones, then your language feature has design
problems.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/f529e3c1-dc44-4a1b-86db-a6a3f5befc89%40isocpp.org.
Mingxin Wang
2017-05-14 06:11:41 UTC
Permalink
Post by Mingxin Wang
Post by Mingxin Wang
Dear Mr. Bengt Gustafsson and Mr. Nicol Bolas,
Thank you for your ideas.
Such requirements really do exist! But I think this is more appropriate
to be a library feature rather than a lanuage feature to *bind* a value
[...]
*With this class, it is possible to use "Wrapper<SomeProxy>" to turn a
Post by Mingxin Wang
proxy into a value wrapper.* But as it is a wrapper (like
"std::reference_wrapper" does), certain member functions are provided to
access the proxy ("get()" and "operator P&").
I think this is enough so far.
The deficiencies relative to a hand-written type-erased type are readily
1) The wrapper takes up more memory.
On my compiler (G++ 6.3.0 x64):

sizeof(std::function<void()>) == 32;
sizeof(Wrapper<Runnable>) == 24;

2) The wrapper is less efficient (since it has two type-erasures instead of
Post by Mingxin Wang
just one).
To test the performance of the wrapper, I did a little experiment on my PC
(with G++ 6.3.0 x64, Command line: g++ -march=corei7-avx -O2 -Wall
-std=c++17):

Experiment A:
void small_demo(); // An ordinary function

constexpr int UPPER = 10000000;

for (int i = 0; i < UPPER; ++i) {
std::function<void()> f(small_demo);
}

Experiment B:
void small_demo(); // An ordinary function

constexpr int UPPER = 10000000;

for (int i = 0; i < UPPER; ++i) {
Wrapper<Runnable> f(small_demo);
}

Experiment C:
class LargeDemo { // A large enough functor that is not able to be stored
in a std::function<void>
public:
void operator()() {}

private:
int a[1000];
};

constexpr int UPPER = 10000000;

for (int i = 0; i < UPPER; ++i) {
std::function<void()> f(large_demo);
}

Experiment D:
class LargeDemo { // A large enough functor that is not able to be stored
in a std::function<void>
public:
void operator()() {}

private:
int a[1000];
};

constexpr int UPPER = 10000000;

for (int i = 0; i < UPPER; ++i) {
Wrapper<Runnable> f(large_demo);
}

The result is shown in the table below:

<Loading Image...>

It is not surprising that std::function<void> is much efficient than
Wrapper<Runnable> with small data sets (after compiler optimizations we
have), because it has been specially constructed. But as you can see from
the table, Wrapper<Runnable> is a little more efficient than
std::function<void> with large data sets. Besides, Wrapper<T> is just
a descriptive implementation, compiler vendors may apply other algorithms
to reach a compromise if necessary.

3) The wrapper is *far* less convenient to actually *use*, since it's not
Post by Mingxin Wang
the actual proxy; you have to use `get` or an implicit conversion to get at
the behavior you want.
If it would be better for a user to hand-write a type-erased type rather
than use your language-based one, then your language feature has design
problems.
Is not the whole point of this feature to make type-erasure a first-class
part of the language? If so, then it needs to be usable by *everyone* who
wants to use type-erasure, not just everyone who wants reference semantics.
Concepts doesn't have this flaw; if you want to pass a value that satisfies
a concept around by value, you can (so long as the concept allows such
copying/moving).
Why can't proxies?
P.S. It is possible for a proxy to proxy any type, including member
Post by Mingxin Wang
auto f = std::bind(...);
Runnable r(f);
But now you have to make sure that `f` stays alive so long as `r` does.
With regular function pointers, you don't have to care; function pointers
are valid forever. As are member pointers, but those don't work here.
If you were to write a hand-written `Runnable<Func>` type, even if it only
has reference semantics, you would still give it native support for member
pointers. Because those are genuine "runnables" and should have first-class
access like function pointers.
That is why I think the "proxies" should be a language feature and the
"wrappers" should be a library feature. Wrappers are not proxies, they are
bound from a proxy and a user-defined implementations, so they should not
have semantics like the "proxies". This is similar with the reason why
std::reference_wrapper do not have the semantics as the wrapped object has.
Post by Mingxin Wang
So again, I find myself saying that if hand-written type-erased types are
better than your language-based ones, then your language feature has design
problems.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/4376b734-9a95-46d5-af3e-0aba3e722501%40isocpp.org.
Jakob Riedle
2017-05-14 15:40:17 UTC
Permalink
Hi Folks,

I really have the feeling, that this discussion is not leading somewhere
helpful anymore.
The challanges a proposal has to face have been outlined very clearly now
by the participants of this thread.

Personally, I find it to be quite hard to argue about something that is not
yet specified to at least 60%.
Even if several concerns seem to be quite applicable, it is up to *a
proposal *to give answers to them.
This thread is IMO not the right spot to work out solutions for every
single detail but rather to identify spots of potential need for
clarification.

Therefore I rather suggest someone to write a proposal.

Jakob


PS: If nobody wants to do that, I'd be glad to do that myself!
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/6f0f7ed7-e7e1-40fb-b783-ccb3a58344a6%40isocpp.org.
Nicol Bolas
2017-05-14 18:00:49 UTC
Permalink
Post by Mingxin Wang
Post by Mingxin Wang
Post by Mingxin Wang
Dear Mr. Bengt Gustafsson and Mr. Nicol Bolas,
Thank you for your ideas.
Such requirements really do exist! But I think this is more appropriate
to be a library feature rather than a lanuage feature to *bind* a value
[...]
*With this class, it is possible to use "Wrapper<SomeProxy>" to turn a
Post by Mingxin Wang
proxy into a value wrapper.* But as it is a wrapper (like
"std::reference_wrapper" does), certain member functions are provided to
access the proxy ("get()" and "operator P&").
I think this is enough so far.
The deficiencies relative to a hand-written type-erased type are readily
1) The wrapper takes up more memory.
sizeof(std::function<void()>) == 32;
sizeof(Wrapper<Runnable>) == 24;
This is an apples-to-oranges comparison. Your `Wrapper` implementation
doesn't use small storage optimization (SSO), but `function` almost
certainly does (as evidenced by your performance tests). If you used a
`function` implementation that didn't use SSO, then you'll find that it
could just be `sizeof(void*)` in size; *maybe* `2 * sizeof(void*)`,
depending on implementation. By contrast, `Wrapper` cannot possibly get any
smaller.

Now, if you implemented `Wrapper` to have an equivalent SSO buffer to that
of `function`, then you would again find that `function` is smaller. Why?
An equivalent SSO-based `Wrapper` implementation would have the same
internal buffer needs as an SSO `function` implementation. But because
`Wrapper` also stores a proxy object, it would need a proxy member
subobject in addition to that storage. So whatever the `function`'s size
is, an SSO `Wrapper` would need `sizeof(Proxy)` more bytes to have
equivalent SSO storage.

There's no way around this; whatever proxy wrapper you use will *always*
take more space than an *equivalent* `function` implementation.

2) The wrapper is less efficient (since it has two type-erasures instead of
Post by Mingxin Wang
Post by Mingxin Wang
just one).
To test the performance of the wrapper,
I wasn't specifically talking about being faster or slower. I said it was
"less efficient". Two type-erasures means two vtables. This means two
vtables for every type you erase.

The wrapper will result in more code generation than `function`. Again,
there's no way around this. How important that is differs from person to
person, but it's an inefficient that doesn't *have to exist*.
Post by Mingxin Wang
I did a little experiment on my PC (with G++ 6.3.0 x64, Command line: g++
<snip>
<https://lh3.googleusercontent.com/-b7s_rR8iY9o/WRfuXWeINII/AAAAAAAAAEs/VnaF4_GJmO4Xgt6X185BDyDHtV0XOjq9QCLcB/s1600/%25E5%259B%25BE%25E7%2589%25871.png>
It is not surprising that std::function<void> is much efficient than
Wrapper<Runnable> with small data sets (after compiler optimizations we
have), because it has been specially constructed.
Please read what you just wrote. You wrote that a hand-written type-erased
type will be faster in many cases than your wrapper. I don't understand how
you can write that and still think your design decision is a good idea.

Look at classes. When they made virtual functions, they didn't define it in
such a way that hand-coded vtables would be *faster* than
compiler-generated ones. Because that would be a *huge defect* in the
language, since it would encourage people *not to use the feature*. If
people decide to *avoid* using a language feature because it's slower than
doing the same thing manually, that's a serious deficiency in that language
feature (see the performance issues of `dynamic_cast` for an example).

There is no reason why the compiler would be unable to generate a value
semantics Runnable proxy that was as efficient or moreso than
`std::function`. The reason it doesn't work here is because your `Wrapper`
type is not a *language feature*; it's a library construct. And therefore
it has to play by the rules of library constructs rather than language
features.

By making it a library construct, you have *denied* the compiler the power
it needs in order to generate the code that it could have.

But as you can see from the table, Wrapper<Runnable> is a little more
Post by Mingxin Wang
efficient than std::function<void> with large data sets. Besides,
Wrapper<T> is just a descriptive implementation, compiler vendors may apply
other algorithms to reach a compromise if necessary.
Unless you have certain knowledge that implementations of `wrapper<T>`
actually can "apply other algorithms to reach such a compromise", then you
should not dismiss such criticism.

Especially since `wrapper<T>` still has plenty of other criticisms, as I've
outlined.

P.S. It is possible for a proxy to proxy any type, including member
Post by Mingxin Wang
Post by Mingxin Wang
Post by Mingxin Wang
auto f = std::bind(...);
Runnable r(f);
But now you have to make sure that `f` stays alive so long as `r` does.
With regular function pointers, you don't have to care; function pointers
are valid forever. As are member pointers, but those don't work here.
If you were to write a hand-written `Runnable<Func>` type, even if it
only has reference semantics, you would still give it native support for
member pointers. Because those are genuine "runnables" and should have
first-class access like function pointers.
That is why I think the "proxies" should be a language feature and the
"wrappers" should be a library feature. Wrappers are not proxies, they are
bound from a proxy and a user-defined implementations, so they should not
have semantics like the "proxies". This is similar with the reason why
std::reference_wrapper do not have the semantics as the wrapped object has.
You're effectively defining the problem domain so that value semantics is
not even a question. `reference_wrapper` doesn't have value semantics
because it's called `*reference*_wrapper`. It is, by definition, not the
point of the type.

I'm saying that the feature you're talking about is *really* "language
support for type-erasure". And that feature *does not* define itself by
creating reference wrappers. So there's nothing to be gained by *preventing*
"language support for type-erasure" from providing value semantics, where a
user wants it. Language support for value-semantics type-erasure is
superior in *every way* to your "wrapper" solution.

While simultaneously not harming the ability to create reference-semantics
type-erasure.

Or to put it another way, how is your feature made better or more effective
at doing its job by *forbidding* value semantics? Because I can't think of
a reason to explicitly disallow people from making type-erasure use value
semantics. Even if you personally would never use a value semantics proxy,
why are you so adamant about *stopping* others from doing so?

It really is like suggesting that lambdas should have always captured
variables by reference, and then saying that you can just write a wrapper
around your reference lambda if you want to store values. That is
technically true, but that doesn't make it a better idea than just allowing
lambdas to capture by copy/move directly.

Just because you *can* get away without something doesn't mean you *should*.
A holistic language feature is better than the minimum you can get away
with + a half-measure for other cases.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/d0154a44-aae6-4e80-a5e9-9cb5312cc5a1%40isocpp.org.
Bengt Gustafsson
2017-05-14 22:05:57 UTC
Permalink
Note: In this text I start by introducing a feature not entirely related to
the matter at hand. However, there is a plot twist at
the end which explains it all!

Indirect ineheritance
=====================

It is sometimes needed to re-implement an API of another class, such as
when implementing type erasure. C++ currently contains one
way of reusing an API, inheritance. Unforunately inheritance implies a by
value instance of the base class which prevents the
polymorphism central to the type erasure technique. To remedy this
situation I have explored an idea of "indirect inheritance" on a
cursory level. By indirect inheritance I mean that the base class part is
separated (in memory) from the subclass part and its
location is instead specified by other means. After some consideration it
seems that the best such "means" is a member function,
which I will call the *indirection function*. This function provides the
necessary flexibility to implement different types of type
erased class templates with different lifetime management strategies which
was hard to achieve with alternate approaches.

Example:
--------

// General handle class with shared ownership.
template<typename T> class shared_proxy : public T(getPtr) {
public:
shared_prioxy(const shared_ptr<T>& src) : mPtr(src) {}
private:
T& getPtr() { return *mPtr; }
const T& getPtr() const { return *mPtr; }

shared_ptr<T> mPtr;
};

This code looks pretty ordinary and it is obvious that shared_proxy<T>
implements the API of T as it inherits from it. What's special
with this code is of course the specification of a method name as
"parameter" to the base class T. This is what indicates
indirect inheritance and specifies how the `this` pointer for the T base
class is to be retrieved.

The actual type erasing consists of the possibility to initiate a
shared_proxy<T> with any subclass of T. This offers
some convenience over a plain shared_ptr in that its members can be
accessed using . instead of -> and that it can be an operand
of function calls or operators without using the * indirection operator.

However, it is almost as easy to create generic by value type erasure,
albeit now we have to handle the deleter functionality
(equvalent of the shared_ptr control block), SOO optimization etc. This is
a major step in convenience and offers an alternate way
of achieving most of the goals of the proposed operator.():

Example:
--------

template<typename T> class value_proxy : public T(getPtr) {
public:
template<typename U> value_proxy(U& src) : mPtr(std::make_deep<U>(src)) {}

private:
T& getPtr() { return *mPtr; }
const T& getPtr() const { return *mPtr; }

deep_ptr<T> mPtr;
};

Sub sub;

void fun(const value_proxy<Base>& par);

// Call fun with subclass object. This creates a hidden copy refered by the
deep_ptr which fun can use and store freely. Still
// retaining the original Sub quality of it.
fun(sub);


Here I use a fictituous deep_ptr which has the ability to clone its pointee
when it itself is copied. Implementing it gets messy but that's
another story...

The compiler generates code at each use of a T member to first call the
indirection function and use
the returned T& value as the this pointer for the base class. Any decent
optimizer will then reduce this to just an unavoidable pointer
indirection, creating, as far as I can see, an optimal implementation from
both memory use and runtime aspects, i.e. identical as a
shared_ptr or deep_ptr itself in these cases.

When the object is used as a parameter to a function or operator taking a
T& the compiler must realize that the T of the proxy is
reached via the indirection function and call it, giving the returned
pointer to the function or operator to be called.

It is possible to allow the indirection function to return a const T& and
then only const methods are
accessible and only const access to members would be allowed (just as
expected). As seen in the examples it is allowed to overload
the indirection function for const/non-const proxy object which enables the
constness of the base class to follow the constness of the
proxy object which is probably desired in most cases. Thus the "paremeter"
of the type is to be considered a name of an overload
set rather than a method pointer.


Duck typing indirect inheritance
================================

Given the indirect inheritance feature it is not entirely far fetched to
extend it to handle the cases discussed in this thread,
that is, when the T and U classes of a value_proxy are not related by
inheritance. This places some additional requirements on the
design. Firstly there must be a hidden function/offset table to map between
function addresses and member offsets of T and U.
Secondly the code that calls the functions or accesses the data members
must be augented similarly. Thirdly there must be some
syntax to indicate the U type at hand in the constructor call. For now I
will use a simple but odd T(U) syntax in the base/member
initializer list.

As for the indirection function there is a choice of still mandating that
it return a T& or to allow it
to return void*. I think the latter is more appropriate as there would have
to be void* types involved anyway, due to the lack of
common base class.

The boilerplate in the proxy object becomes a bit more complicated when
there is no common base class but the usual trick of in
place constructing an object with virtual destruct and clone methods makes
this work. In this example I assume that a
deep_ptr<void> specialization is available which implements this for us:


template<typename T> class proxy : public T(getPtr) {
public:
template<typename U> proxy(U& src) : T(U), mPtr(std::make_deep<void>(src))
{}

private:
void* getPtr() { return *mPtr; }
const void* getPtr() const { return *mPtr; }

deep_ptr<void> mPtr;
};

struct First {
void func(int, float);
int x;
};

struct Second {
void func(int, float);
int y;
int x;
};

Second sec;

void fun(const proxy<First>& par);

// Call fun with duck typed `Second` object. This creates a hidden copy
refered by the deep_ptr<void> which fun can use and store freely. Still
// retaining the original `First` quality of it.

proxy<First> pf = Sec;
pf.func(1, 3.14); // Calls Second.func.

fun(sec); // Implicitly converts sec to a proxy object.


The compiler uses First to create a function/offset table layout when it
sees the first proxy<First>. When it sees the constructor
call proxy<First>::proxy<Second>() at the initialization of pf it creates
an instance (during compile time) of this table layout
containing the corresponding method addresses and member offsets for Second.

The T base class now consumes space for one pointer to store the address of
the function/offset table and this gets set to the
appropriate table by the code generated for the specific template
constructor.

When the duck typed base class methods are called the compiler must
generate not only the call to the indirection function, but
also defer the actual function call via the function/offset table just like
a virtual function call. Even if the method in T
itself is virtual I don't think we should require two level dispatch, which
would be needed if the constructor parameter to proxy
is really a reference to a subclass of U. This is as cloning the object
using the helper object created for the class U would still
slice the object, so this construct will still require that the ctor sees
the actual type of the provided instance. This is the same
problem as with deep_ptr itself, as debated in another thread. On the other
hand this slicing would not occur for by-reference
proxies. Possibly both policies can be allowed by adorning the base class
name with a modifier such as virtual (although this has
totally different meaning than for current virtual bases).

At each access to a data member an offset from the function/offset pointer
table would have to be added to the return value of the
indirection function instead of just adding a fixed offset to this for
normal data member access. This is an unavoidable overhead
for the type erasure.

There seems to be no way that the proxy<T> can behave as a T when used as a
by reference or by pointer function or operator
operand. This is of course due to the fact that there is actually no T
object to refer to. While this is unfortunate it seems
impossible to avoid so the only remedy would be to let those functions and
operators take proxy<T>& as parameter, with the
associated runtime penalty, or make them templates (taking T or proxy<T>)
with the associated compile time/object size cost. I
think this the major drawback of this entire idea, as it gets hard to teach
and limits the usefulness.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/1b64712a-d9d5-4e93-82fd-1dfd2c5eddcf%40isocpp.org.
Mingxin Wang
2017-05-15 08:05:00 UTC
Permalink
Dear Mr. Bengt Gustafsson,

I think a proxy is something behaves like an ordinary reference (T&, rather
than std::reference_wrapper<T>) with polymorphism, but more flexible than a
reference because

- a reference can only represent one type, while a proxy can represent
any type that have specific expressions and semantics, and
- a proxy has more comprehensive syntax for it is DefaultConstructible,
CopyConstructible, MoveConstructible, CopyAssignable and MoveAssignable.

With this idea, it is even possible for multiple proxies with different
semantics to represent one object with a little runtime overhead.

But it is relatively difficult for beginners to write C++ code directly
with the proxies, because proxies are not responsible for the life cycle of
the object being represented at all. In order to make it more friendly to
beginners, the class template "Wrapper" is designed, so that users are able
to bind an object with a proxy. With the help of the wrapper that binds an
object with the proxy, on the one hand, users are able to access the
wrapped object with a proxy (with
"Wrapper<SomeProxy>::get()::some_method(...)"), and there is no need to
consider the lifecycle management issue for the wrapped object. On the
other hand it narrows the scope of a proxy's function, meanwhile, may
introduce much runtime overhead for the lifecycle management.

*When it comes to managing the life cycle of a wrapper, I think it is an
issue that has nothing to do with the above.* Users are free to declare a
wrapper as a "stack object" or manage it with a smart pointer, e.g.
"std::unique_ptr<Wrapper<SomeProxy>>" or
"std::shared_ptr<Wrapper<SomeProxy>>", etc.

Mingxin Wang
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/86056e1e-28a0-4410-a332-454d50b7b331%40isocpp.org.
Bengt Gustafsson
2017-05-16 21:46:14 UTC
Permalink
I really do think that the feature I sketched in my last writing caters for
the needs you have identified. I do realize that it uses a quite different
method to achieve the goals, and the code examples I showed are just the
kind of "wrappers" that you (correctly) think will be necessary to get the
feature used.

I also hope that my approach corrects the shortcomings regarding lifetime
management that Nicol and others have pointed out with your approach, and
which I have not found any means to correct within the scope of that
approach. Furthermore I achieve this without having to invent a new type of
"struct declaration" and instead reuse the one we have.

To complement the solution for the complete "proxy" case my approach offers
a *light* version if you do have a common base class that the types you
want to type erase inherit from, this version having no runtime overhead
whatsoever compared to the underlying storage management (exemplified by
the shared_ptr or deep_ptr).

Maybe I did a pedagogical mistake in placing the *light* version at the top
of the writing, which is why I wrote about the plot twist to keep you guys
reading. To simplify here is a short form "gist" of the feature suggested:

- Indirect inheritance inherits functionality from an object whose
this-pointer is retrieved by calling a method on each use.

- Duck typed indirect inheritance augments this functionality by providing
a "compliant" type in the ctor's initializer list, which lets the compiler
create the required translation table.

For more info including rationale and examples see the previous post.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/dafadc7f-a99b-4824-b975-124fd08a3b34%40isocpp.org.
Nicol Bolas
2017-05-13 16:11:40 UTC
Permalink
Post by Nicol Bolas
Things your design is potentially lacking (which aren't covered in the
Post by Nicol Bolas
Proxies need an equivalent of `dynamic_cast`. Or rather, an equivalent of
`any_cast`, which is effectively the same thing. You need to be able to
extract the type back out of the proxy. And it must be the exact type you
provided the proxy.
Also, I think we need proxy maps. Remember the old C++11 concepts with
concept maps, which were basically ways to coerce a type to fit a concept
it doesn't really fit? I think this would be *very* useful for proxies.
A proxy map is a type which implements a particular proxy for a
particular given type(s), as defined as a template. Consider a callable
proxy. That works well enough for class types, but what if you want your
callable to be able to use function pointers or member pointers? You need
some kind of class type to do the translation there.
A function can be regarded as a const reference to a pointer, so it is OK
proxy Runnable {
void operator()();
};
void fun();
Runnable r(fun);
r();
And member pointers?

That would be the point of the proxy map. For a given proxy and for a given
Post by Nicol Bolas
Post by Nicol Bolas
type (or range of types), you create a class. When a user tries to turn one
of those types into that class, it results in the construction of a proxy
map type with that value, which is then passed into the proxy type.
Unless you can come up with a more elegant way to handle non-class types,
or types that don't quite fit a proxy.
Basically, my litmus test for proxy design is this: the design is
deficient if you cannot implement `std::function` by using a proxy.
I think std::function can be replaced by the proxy in many situations
with less type requirements (types are not required to be
CopyConstructible) and lower runtime overhead (as I roughly tested, *std::function<void()>
is approximately 3 times slower to construct than the proxy Runnable*
with ordinary function pointers on my PC).
What If I *want* a CopyConstructible proxy? I'm not saying reference
proxies are bad; I'm saying that the idea of type-erased classes should not
simultaneously *require* that they only store references.

Consider the member function issue. You say that your `Runnable` proxy
should be able to work with a fundamental type like function pointers. But
it can't work with member functions, since those require some form of
intermediary to convert the funky .*() call syntax into regular operator()
syntax.

So what happens if you try to do this:

Runnable func(std::mem_fn(&Typename::MemFunc));

That's putting a temporary into a reference type. That doesn't work very
well.

Now yes, we would *all* like to just make member pointers callable with ()
like regular function pointers. But sadly, the committee rejected that
for... reasons. So we have to make due with the language we've got.

But don't think that the utility of this kind of intermediate object tool
ends with member pointers. There are many cases where you have some type
that *almost* fulfills an interface. The way to deal with that is to make a
wrapper of some kind to fill in the gaps. And wrappers will generally
created as needed through temporaries (like `std::mem_fn`), rather than
used directly.

Type-erased objects are very useful for many things. But limiting them to
references to the type-erased object limits your ability to do things like
the above. Where you create a type to act as an intermediary to fill in an
interface that doesn't quite fit the proxy's definition.

As to what efficiencies can be gained by allowing the compiler to generate
value semantics proxies, that depends on the cleverness of compiler
vendors. The thing I'd be most interested in is the possibility for
allowing the user to specify allocators that also get type-erased.
`std::function` used to have constructors that took allocators, but they
were fundamentally unworkable and were removed. But compilers can do all
kinds of things that we can't; they might be able to implement type-erased
allocators for value proxies even when we can't.

The reason why std::function requires the concrete implementation to be
Post by Nicol Bolas
CopyConstructible is perhaps that polymorphism is usually accompanied by
the use of containers (otherwise, we can use template), and containers
often requires the specified types to be CopyConstructible, thus
std::function requires the types it wraps to be CopyConstructible.
Um, no. The reason why `std::function` uses value semantics is because it
makes code like the above `Runnable` example actually *work*. It allows you
to use `mem_fn`, `bind`, lambda expressions, and all kinds of other things
that you could never use with reference semantics callables.

Because the “proxies” is an update version for virtual functions, *it can
Post by Nicol Bolas
Post by Nicol Bolas
Post by Mingxin Wang
proxy any expression that can be declared virtual, including the overloads
and operators, but is not able to proxy the expressions that cannot be
declared virtual*, e.g. inner types, constructors.
No.
We're talking about a *language feature*. You should not limit the
behavior of a language feature simply because the analogy no longer fits.
If a "proxy" *genuinely* cannot be implemented which can forward certain
constructs, so be it. But you shouldn't limit the design just to things
that can be declared with virtual functions simply because it doesn't fit
into the analogy you built for it.
That doesn't mean we have to do it through concepts, of course. But the
idea that you *can't* have a proxy include non-member functions just
because you can't have non-member virtual functions is needlessly limited.
I wrote this only to limit the expressions that shall appear in a proxy
definition. Particularly, an expression in the implementation is not
necessary to be a member function, it can even be static. It is defined
that a proxy can and can only proxy the expressions that are able to be
declared virtual, because C++ has done a lot in the virtual function
mechanism, and there is enough research in what expressions are suitable
for runtime polymorphism.
But "expressions" cannot be "declared virtual"; *member functions* are
declared virtual. Therefore, if we translate what you said into
standardese, the only conclusion is that the only things that can appear in
a proxy definition are member functions.

If that's not the intent, then please be more clear as to exactly what can
appear in a proxy definition.

*Ensure proper ownership management*
Post by Nicol Bolas
Post by Nicol Bolas
Post by Mingxin Wang
When it comes to lifetime handling I think that Jakob Riedle is right in
that we should strive for getting the same type of semantics as for regular
types, as he showed in his code box. I think this means that there must be
some more magic in the proxy code as it will optionally own the data
itself. The sad part is that now it becomes hard to see how shared_ptr<T>
and shared_ptr<MyProxy> could co-exist pointing to the same T object (T
fulfilling MyProxy of course). But this may be a necessary sacrifice as we
can't support all user written shared pointers out there anyway. Or maybe
it can be supported (similarly to how a shared_ptr<BASE> can be assigned
from a shared_ptr<DERIVED> and they still share the refcount. This will
require some surgery in the shared_ptr class I suspect. Thinking about the
details of this will reveal new insights, no doubt.
This is a question I've been thinking about over and over again. On the
one hand, if the lifetime issue is coupled with the “proxies”, it violates
the single responsibility principle, and it becomes unfriendly to “stack
objects”, whose lifetime is controlled by the execution of program, but it
seems to be easier to use. On the other hand, if the lifetime issue is
decoupled from the “proxies”, users are responsible for managing the
lifetime, however, they are free to specify the algorithms (for “heap
objects”, from new/delete to GC).
After some research, I found it unnecessary to couple the lifetime issue
with the “proxies” at all, because most situations that require
polymorphism are implementable without dynamic memory allocation, as the
example mentioned earlier demonstrates.
Really? Because `std::function` disagrees with you. `std::function` is
functionally a proxy for a type that is callable with the `operator()`
overload specified by the `function`'s template parameter. And yet, it also
requires copyable value semantics.
So clearly there is a genuine need for type-erasure+value semantics. Your
personal use cases may not need it, but if `function` (and `any` for that
matter) are any indication, combining the two is something people really do
use.
As you think copying the wrapped object is sometimes necessary, could you
provide a meaningful use case which is inconvenient to implement with the
proxy or may reduce runtime overhead while implemented with std::function?
OK, let's say you want to use a reference proxy to implement a copyable
value proxy. To do that, you have to store the reference proxy, which is
going to take up (at least) a pointer of storage. You also need to store
sufficient space for your own pointer to your own type-erased allocated
object (possibly with small storage optimization). You can't use the
proxy's storage because you don't know the type to extract from the proxy
in order to delete it. So you need to store your own type-erasure machinery
to do the actual deleting.

So a `std::function` implemented in terms of a reference proxy would be
less efficient than `std::function` implemented by itself. As would any
other value-semantics type implemented around a reference.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/332280b7-e28d-494b-bd09-dc07ecd3baa8%40isocpp.org.
Bryce Glover
2017-05-15 00:55:03 UTC
Permalink
(snipped
)
Implementing a way to hide the parts of itself that a derived class inherits from its parent base class behind a quasi-opaque, type-erasing ‘pointer to implementation’ from which it could then retrieve this information sounds like it could be interesting and could eventually end up being useful to somebody, but I’m not entirely certain how well the introduction of such a concept contributes to the discussion undertaken in this thread so far. Perhaps one could implement a fully functional run-time concept wrapper implementing properly forwarding versions of the functionality expected of the type of objects the compile-time concept associated with this wrapper would require it to contain using this technique, but I feel both that a run-time concept facility should be easy to use by language users with abilities nearer to beginner level than this particular iteration of this construct and that said iteration doesn’t feel quite right to me when it comes to usability and understandability, but you should probably trust other peoples’ reactions more than mine on this front, as I’m not the best person to give feedback on these kinds of matters. In any case, it strikes me that a version of this ‘indirect inheritance’ that one might consider more acceptably traditional would involve classes satisfying concepts related by subsumption, but that doesn’t really describe the model you’ve come up with. Also, the part of your post where you started talking about maintaining object dispatch tables on top of the virtual function dispatch tables that already exist reminded me indirectly of some of the work that’s already been done to describe and even implement open multimethods, but there’s a chance that this impression of mine could be somewhat misguided. The additional complexity you consider at the end probably doesn’t help, either, but, again, I’m not really the best individual to go through all of this with somebody despite wanting to use it myself.

— Bryce Glover
  ***@gmail.com
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/18DFAA3F-F4BB-4A9B-8B77-2F780F5BA4DE%40gmail.com.
Bryce Glover
2017-05-16 00:12:32 UTC
Permalink
Post by Bryce Glover
(snipped
)
Implementing a way to hide the parts of itself that a derived class inherits from its parent base class behind a quasi-opaque, type-erasing ‘pointer to implementation’ from which it could then retrieve this information sounds like it could be interesting and could eventually end up being useful to somebody, but I’m not entirely certain how well the introduction of such a concept contributes to the discussion undertaken in this thread so far.
Well, my intention was for this to become clear towards the end of the post, where the "proxy/interface" version of the indirect inheritance feature is discussed. I already had the indirect inheritance idea back when operator.() was discussed, as an alternative way to implement the handle classes it was aiming at. The main problem with operator.() was how to handle the ambiguity if both the handle class and its "T" had a method of the same name, which was solved in a tried and true way by viewing this as inheritance (i.e. the handle class method takes precedence and hides the T method unless a T:: is prefixed at the call site).
When the proxy/interface discussion started it became obvious that the requirements were similar: A template class is to implement the API of its template parameter T by some kind of forwarding, but in this case involving a translation table.


Perhaps I’ve just gotten so tired of watching this discussion go around in circles even though I haven’t really contributed all that much to it that my eyes started to glaze over, thus explaining why I didn’t exactly notice how you meant your thoughts to flow when I first skimmed your description of them. Rereading your original posting of this idea to this thread, as well as processing it in light of the additional context you’ve just provided here, has, I feel, let me start to catch on to where you’ve been trying to go with all of this. I remain somewhat confused, however, as to why you can’t seem to figure out how to cleanly separate the aspect of this mechanism involving a ‘has a’ relationship of type composition (that is, containment) from that related to the ‘is a’ relationship enforced by inheritance, but I’m starting to appreciate how hard it might turn out to attempt disentangling these two orthogonal properties of a ‘forwards to’ relationship when the forwarding object masquerades as its target object from how you’re explaining things. Still, I can’t help but feel there could be an easier way to cut this Gordian knot despite not knowing how best to present my misgivings on this front.


My writeup is an attempt at showing how these two features can be stacked to achieve both goals.


No wonder I felt dizzy, then, what with the number of indirections involved and all! I couldn’t keep track of them, as they keep piling up! (Maybe I should just duck back out again and watch those of you with more expertise in matters like these hash things out, now that I think about it
) In any case, t’d probably understand your proposal for how virtual concepts should work if you presented it separately from this `operator .()`-related ‘indirect inheritance idea, if asking you to do that is in any way a reasonable request.


Admittedly my approach is fairly different from the original proposer's who appointed the "magic features" to the new type of "thing" he called interface or proxy, while in my approach this declaration is just a regular struct/class declaration and the magic is in the indirect inheritance declaration.


It reduces the surface area of the new functionality required to implement language-level type erasure, though, so that’s good.


Post by Bryce Glover
Perhaps one could implement a fully functional run-time concept wrapper implementing properly forwarding versions of the functionality expected of the type of objects the compile-time concept associated with this wrapper would require it to contain using this technique, but I feel both that a run-time concept facility should be easy to use by language users with abilities nearer to beginner level than this particular iteration of this construct and that said iteration doesn’t feel quite right to me when it comes to usability and understandability, but you should probably trust other peoples’ reactions more than mine on this front, as I’m not the best person to give feedback on these kinds of matters. In any case, it strikes me that a version of this ‘indirect inheritance’ that one might consider more acceptably traditional would involve classes satisfying concepts related by subsumption, but that doesn’t really describe the model you’ve come up with.
Isn't this exactly what I describe in the first half of the writeup? This still requires that the _actual_ object referred to is a subclass of T and thus, to the best knowledge of the compiler, satisfied the Liskov substitution principle (I looked this up in Wikipedia as I didn’t know what subsumption ment).


Well, the entire point of concepts, run-time or compile-time, is that you can throw inheritance right out the window, right? As long as the target object another, different object is working with exposes the interface it expects, everything should work properly, as I understand it. In addition, when I referenced ’subsumption,’ I really meant concept refinement: it shouldn’t matter whether the target you’re handling via some interface uses it to fulfill either only the requirements expected of itself by an object of a type designed to handle or work with it or a superset of them no matter what conceptual constraints the handle type expects of its target as long as the resulting behavior stays identical, correct?


Post by Bryce Glover
Also, the part of your post where you started talking about maintaining object dispatch tables on top of the virtual function dispatch tables that already exist reminded me indirectly of some of the work that’s already been done to describe and even implement open multimethods, but there’s a chance that this impression of mine could be somewhat misguided.
Yes, you are mistaken, this does not solve multimethod issues, it is related to the case that the constructor src operand that the compiler thinks is a U is really a subclass of U reimplementing any or all of its virtual methods, thus requiring both a step to come from the "apparent" type T to U and a separate step from U to its subclass method (as the first step is fixed at compile time for each call site while the latter can vary for each invokation of the call site).


What I meant is that the impression I originally got was that this might involve something similar to the dispatch tables required for multimethods to work properly, but now I see that what you’ve been trying to describe bears more similarity to Objective-C method swizzling <http://nshipster.com/method-swizzling/> than it does to the former, albeit not at bind/link time.


Post by Bryce Glover
The additional complexity you consider at the end probably doesn’t help, either, but, again, I’m not really the best individual to go through all of this with somebody despite wanting to use it myself.
As I point out it only allows using the proxy<T> containing a U in some contexts, i.e. when the T is the “this" object.


What about non-owning proxies, though? And what if I wanted to have a `proxy_of_one_type<proxy_of_another_type<U>>`?


Post by Bryce Glover
— Bryce Glover
--
Bengt Gustafsson
CEO, Beamways AB
Westmansgatan 37
582 16 Linköping, Sweden
+46 (705) 338259
Skype: benke_g
www.beamways.com <http://www.beamways.com/>
Still pondering,
Bryce Glover
***@gmail.com
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/6AD8B3FD-9D97-433D-A4BC-DFD7E665159E%40gmail.com.
Bryce Glover
2017-05-17 23:51:00 UTC
Permalink
I really do think that the feature I sketched in my last writing caters for the needs you have identified. I do realize that it uses a quite different
method to achieve the goals, and the code examples I showed are just the kind of “wrappers" that you (correctly) think will be necessary to get the feature used.
I also hope that my approach corrects the shortcomings regarding lifetime management that Nicol and others have pointed out with your approach, and which I have not found any means to correct within the scope of that approach. Furthermore I achieve this without having to invent a new type of “struct declaration" and instead reuse the one we have.
To complement the solution for the complete "proxy" case my approach offers a *light* version if you do have a common base class that the types you want to type erase inherit from, this version having no runtime overhead whatsoever compared to the underlying storage management (exemplified by the shared_ptr or deep_ptr).
- Indirect inheritance inherits functionality from an object whose this-pointer is retrieved by calling a method on each use.
- Duck typed indirect inheritance augments this functionality by providing a "compliant" type in the ctor's initializer list, which lets the compiler create the required translation table.
For more info including rationale and examples see the previous post.
Dear Bengt,

Are responding to me or to Mingxin Wang, the OP, here? I’m not him.

A tad confused here,
Bryce Glover
***@gmail.com <mailto:***@gmail.com>

P. S.: At this point, I’ve decided I’m going to duck out of discussions on this topic here and elsewhere. Nobody involved needs a potential novice end-user like myself continuing to ask stupid questions that have probably already been considered during such a feature’s design phase, so I’m going back to lurking and waiting for new proposals (I do read the mailings and papers that might end up in them just to keep up with what’s happening, after all.)
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/3FFB97B5-FB86-4469-9BBB-FB0CE8905FF5%40gmail.com.
Mingxin Wang
2017-05-18 14:26:29 UTC
Permalink
According to your suggestions, I thought it over and carefully
reconstructed the wrapper that supports SSO optimization (sorry that I was
not familiar with this term before) as a prototype. Now the reconstructed
Wrapper<Runnable, 16> (16 is the maximum size of "small object" supported
for SSO optimization) *has the same size as std::function<void()>* (whose
size is 32, on the compiler I use, GCC 6.3.0) does.

In order to make the wrapper smaller, I deleted the proxy declaration in
the class template Wrapper, and *now the proxy is passed by value instead
of by reference* (see the implementation for more details).

After some performance tests, I found the newly implemented wrapper is more
efficient with copy constructor, template constructor (construct from an
arbitrary type that suitable for the specified proxy), destructor and
invoking (invoke the stored callable object with the proxy obtained by
Wrapper<Runnable>::get_proxy()), but less efficient with move constructor
than the implementation of std::function<void()> in GCC 6.3.0. Is there
something missing in the implementation? Or this is enough to prove that
the "proxies" is acceptable? After all, there is nothing we have that
supports this feature (except for std::function that only supports callable
objects) in the standard or TS.

The newly built implementation for the class template Wrapper is shown
below:

/* Holds a contiguous memory segment, sizeof(MemoryBlock<SIZE>) == SIZE */
template <std::size_t SIZE>
class MemoryBlock {
public:
MemoryBlock() = default;
MemoryBlock(MemoryBlock&&) = default;
MemoryBlock(const MemoryBlock&) = default;
MemoryBlock& operator=(MemoryBlock&&) = default;
MemoryBlock& operator=(const MemoryBlock&) = default;

/* Access to the memory segment */
void* get() { return data_; }

private:
char data_[SIZE];
};

// P: A proxy type
// SSO_SIZE: The maximum size for SSO optimization
template <class P, std::size_t SSO_SIZE = 16>
class Wrapper {
public:
/* Default constructor */
Wrapper() { init(); }

/* Initialize the wrapper with a concrete data */
template <class T>
Wrapper(T data) { init(std::move(data)); }

/* Copy constructor */
Wrapper(const Wrapper& rhs) {
// There are two situations:
// 1. rhs in invalid,
// *this shall also be invalid.
// 2. rhs is valid, no matter whether SSO optimization is activated,
// *this shall be initialized with rhs.
if (rhs.holder_ == nullptr) {
init();
} else {
rhs.holder_->init(*this);
}
}

/* Move constructor */
Wrapper(Wrapper&& lhs) {
// There are three situations:
// 1. lhs in invalid,
// *this shall also be invalid.
// 2. lhs is valid and SSO optimization is activated,
// *this shall be initialized with lhs.
// 3. lhs in valid and SSO optimization is inactivated,
// The pointer of the holder can be simply moved from lhs to *this.
if (lhs.holder_ == nullptr) {
init();
} else if (lhs.holder_ == lhs.sso_block_.get()) {
lhs.holder_->init(*this);
} else {
holder_ = lhs.holder_;
lhs.holder_ = nullptr;
}
}

/* Destructor */
~Wrapper() {
// There are two situations:
// 1. SSO optimization is activated,
// The destructor of the holder shall be called without release
the memory.
// 2. SSO optimization is inactivated,
// The pointer shall be deleted.
// If holder_ is a null pointer, this operation haves no
side-effect.
if (holder_ == sso_block_.get()) {
holder_->~AbstractHolder();
} else {
delete holder_;
}
}

/* Access to the proxy */
P get_proxy() { return holder_->get_proxy(); }

private:
/* The base class for lifetime management */
class AbstractHolder {
public:
/* Virtual destructor */
virtual ~AbstractHolder() {}

/* Initialize another wrapper with *this */
virtual void init(Wrapper&) = 0;

/* Get a proxy */
virtual P get_proxy() = 0;
};

template <class T>
class ConcreteHolder : public AbstractHolder {
public:
/* Constructors */
ConcreteHolder(T&& data) : data_(std::forward<T>(data)) {}
ConcreteHolder(const T& data) : data_(data) {}
ConcreteHolder(ConcreteHolder&&) = default;
ConcreteHolder(const ConcreteHolder&) = default;

/* Initialize the wrapper with the data (copy construct) */
void init(Wrapper& wrapper) override { wrapper.init(data_); }

P get_proxy() override { return P(data_); }

private:
T data_;
};

/* Initialize *this to be invalid */
void init() { holder_ = nullptr; }

/* Overload for small object */
template <class T>
void init(T&& data) requires (sizeof(T) <= SSO_SIZE) {
// Let holder_ point to the reserved SSO block, and SSO optimization is
activated
holder_ = reinterpret_cast<AbstractHolder*>(sso_block_.get());

// Call the constructor of the ConcreteHolder without memory allocation
new (reinterpret_cast<
ConcreteHolder<std::remove_reference_t<T>>*>(sso_block_.get()))
ConcreteHolder<std::remove_reference_t<T>>(std::forward<T>(data));
}

/* Overload for large object */
template <class T>
void init(T&& data) requires (sizeof(T) > SSO_SIZE) {
// Let holder_ point to a "new" object, and SSO optimization is
inactivated
holder_ = new
ConcreteHolder<std::remove_reference_t<T>>(std::forward<T>(data));
}

/* Associates with the lifetime management strategy */
AbstractHolder* holder_;

/* A reserved block for SSO optimization */
MemoryBlock<sizeof(ConcreteHolder<MemoryBlock<SSO_SIZE>>)> sso_block_;
};

Mingxin Wang
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/6bd5959d-2ed7-411e-a489-c3f42362d0e9%40isocpp.org.
Loading...