Adding the Keyword “proxy” in C++ [update for the "interface"]

490 views
Skip to first unread message

Mingxin Wang

unread,
May 10, 2017, 6:46:22 AM5/10/17
to ISO C++ Standard - Future Proposals
This is an update version for another topic “Adding the Keyword “interface” in C++” 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

Jakob Riedle

unread,
May 10, 2017, 8:10:35 AM5/10/17
to ISO C++ Standard - Future Proposals


Am Mittwoch, 10. Mai 2017 12:46:22 UTC+2 schrieb Mingxin Wang:
  • Changing the keyword “interface” into “proxy” (thanks to Thiago Macierira, Myriachan and Jakob Riedle), and
I did not vote for this :)
My opinion on this is still what Barry Revzin said:
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 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:

Lets go ahead with what you said: 
  • 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 = /**/;
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!

Nicol Bolas

unread,
May 10, 2017, 11:06:45 AM5/10/17
to ISO C++ Standard - Future Proposals
On Wednesday, May 10, 2017 at 6:46:22 AM UTC-4, Mingxin Wang wrote:
This is an update version for another topic “Adding the Keyword “interface” in C++

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.

Mingxin Wang

unread,
May 10, 2017, 1:14:00 PM5/10/17
to ISO C++ Standard - Future Proposals
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:
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. 

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:


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


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 types) in a container and perform some operations with them (only "operator*", "operator++" and "operator==" are used), we may write the following code with "interfaces":
template <class T>
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

Michał Dominiak

unread,
May 10, 2017, 1:20:37 PM5/10/17
to std-pr...@isocpp.org
On Wed, May 10, 2017 at 7:14 PM Mingxin Wang <wmx16...@163.com> wrote:
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.)

Mingxin Wang

unread,
May 11, 2017, 4:32:12 AM5/11/17
to ISO C++ Standard - Future Proposals
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

Michał Dominiak

unread,
May 11, 2017, 4:42:40 AM5/11/17
to std-pr...@isocpp.org
On Thu, May 11, 2017 at 10:32 AM Mingxin Wang <wmx16...@163.com> wrote:
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.

The person writing the wording for the feature can promise 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-proposal...@isocpp.org.
To post to this group, send email to std-pr...@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.

Jakob Riedle

unread,
May 12, 2017, 5:59:53 AM5/12/17
to ISO C++ Standard - Future Proposals
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

Nicol Bolas

unread,
May 12, 2017, 10:49:48 AM5/12/17
to ISO C++ Standard - Future Proposals


On Friday, May 12, 2017 at 5:59:53 AM UTC-4, Jakob Riedle wrote:
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?
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.

Hyman Rosen

unread,
May 12, 2017, 11:08:48 AM5/12/17
to std-pr...@isocpp.org
On Fri, May 12, 2017 at 10:49 AM, Nicol Bolas <jmck...@gmail.com> wrote:
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?

Nicol Bolas

unread,
May 12, 2017, 11:16:27 AM5/12/17
to ISO C++ Standard - Future Proposals

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.

Hyman Rosen

unread,
May 12, 2017, 12:05:56 PM5/12/17
to std-pr...@isocpp.org
On Fri, May 12, 2017 at 11:16 AM, Nicol Bolas <jmck...@gmail.com> wrote:
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?

Mingxin Wang

unread,
May 12, 2017, 12:41:38 PM5/12/17
to ISO C++ Standard - Future Proposals
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.


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 method with a signature like:
 
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 method as in:

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) 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 hard to figure out a useful syntax in another way here we go:
 
std::box<Callable<void()>> myFunction;      // Works essentially as std::function
std::box<MyConcept> x;

Then we can have two functions:

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

inkwizyt...@gmail.com

unread,
May 12, 2017, 2:20:56 PM5/12/17
to ISO C++ Standard - Future Proposals

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.

Hyman Rosen

unread,
May 12, 2017, 2:27:22 PM5/12/17
to std-pr...@isocpp.org

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?
 

inkwizyt...@gmail.com

unread,
May 12, 2017, 2:41:55 PM5/12/17
to ISO C++ Standard - Future Proposals
 
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.

Hyman Rosen

unread,
May 12, 2017, 3:20:45 PM5/12/17
to std-pr...@isocpp.org
On Fri, May 12, 2017 at 2:41 PM, <inkwizyt...@gmail.com> wrote:
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.

Ville Voutilainen

unread,
May 12, 2017, 3:50:41 PM5/12/17
to ISO C++ Standard - Future Proposals
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.

Nicol Bolas

unread,
May 12, 2017, 5:28:02 PM5/12/17
to ISO C++ Standard - Future Proposals
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.


On Friday, May 12, 2017 at 12:41:38 PM UTC-4, Mingxin Wang wrote:
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)).

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 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.


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!

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 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

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.

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.

Mingxin Wang

unread,
May 13, 2017, 6:21:06 AM5/13/17
to ISO C++ Standard - Future Proposals
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.

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();
 
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 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.
 
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.

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?

Bengt Gustafsson

unread,
May 13, 2017, 9:29:14 AM5/13/17
to ISO C++ Standard - Future Proposals
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?

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 = /**/;
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 

Bengt Gustafsson

unread,
May 13, 2017, 9:42:23 AM5/13/17
to ISO C++ Standard - Future Proposals
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.

Nicol Bolas

unread,
May 13, 2017, 12:11:40 PM5/13/17
to ISO C++ Standard - Future Proposals
On Saturday, May 13, 2017 at 6:21:06 AM UTC-4, Mingxin Wang wrote:
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.

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();

And member pointers?

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).

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 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 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

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.

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.

Mingxin Wang

unread,
May 13, 2017, 10:24:29 PM5/13/17
to ISO C++ Standard - Future Proposals
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

Nicol Bolas

unread,
May 13, 2017, 10:56:16 PM5/13/17
to ISO C++ Standard - Future Proposals
On Saturday, May 13, 2017 at 10:24:29 PM UTC-4, Mingxin Wang wrote:
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:
 
[...]
 
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.

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 functions, as long as they are not temporary variables:
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.

Mingxin Wang

unread,
May 14, 2017, 2:11:41 AM5/14/17
to ISO C++ Standard - Future Proposals
On Sunday, May 14, 2017 at 10:56:16 AM UTC+8, Nicol Bolas wrote:
On Saturday, May 13, 2017 at 10:24:29 PM UTC-4, Mingxin Wang wrote:
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:
 
[...]
 
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.

The deficiencies relative to a hand-written type-erased type are readily apparent:

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 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:


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 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 functions, as long as they are not temporary variables:
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.

Jakob Riedle

unread,
May 14, 2017, 11:40:17 AM5/14/17
to ISO C++ Standard - Future Proposals
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!

Nicol Bolas

unread,
May 14, 2017, 2:00:50 PM5/14/17
to ISO C++ Standard - Future Proposals
On Sunday, May 14, 2017 at 2:11:41 AM UTC-4, Mingxin Wang wrote:
On Sunday, May 14, 2017 at 10:56:16 AM UTC+8, Nicol Bolas wrote:
On Saturday, May 13, 2017 at 10:24:29 PM UTC-4, Mingxin Wang wrote:
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:
 
[...]
 
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.

The deficiencies relative to a hand-written type-erased type are readily apparent:

1) The wrapper takes up more memory.

On my compiler (G++ 6.3.0 x64):

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 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.
 
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):
<snip>

The result is shown in the table below:


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 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 functions, as long as they are not temporary variables:
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.

Bengt Gustafsson

unread,
May 14, 2017, 6:05:57 PM5/14/17
to ISO C++ Standard - Future Proposals
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.

Bryce Glover

unread,
May 14, 2017, 8:55:25 PM5/14/17
to Bengt Gustafsson :, std-pr...@isocpp.org
(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

Mingxin Wang

unread,
May 15, 2017, 4:05:00 AM5/15/17
to ISO C++ Standard - Future Proposals
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

Bryce Glover

unread,
May 15, 2017, 8:12:37 PM5/15/17
to Bengt Gustafsson, std-pr...@isocpp.org
On May 15, 2017, at 2:22 PM, Bengt Gustafsson <bengt.gu...@beamways.com> wrote:

Den 2017-05-15 kl. 02:55, skrev 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.  


 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?  


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 than it does to the former, albeit not at bind/link time.  


 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>>`?  



— Bryce Glover

-- 
Bengt Gustafsson
CEO, Beamways AB
Westmansgatan 37
582 16 Linköping, Sweden
+46 (705) 338259
Skype: benke_g
www.beamways.com
Still pondering, 
     Bryce Glover

Bengt Gustafsson

unread,
May 16, 2017, 5:46:14 PM5/16/17
to ISO C++ Standard - Future Proposals
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.

Bryce Glover

unread,
May 17, 2017, 7:51:09 PM5/17/17
to Bengt Gustafsson, std-pr...@isocpp.org
On May 17, 2017, at 6:13 PM, std-pr...@isocpp.org wrote:

Bengt Gustafsson <bengt.gu...@beamways.com>: May 16 02:46PM -0700 

Dear Bengt, 

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

A tad confused here, 
     Bryce Glover

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.)  

Mingxin Wang

unread,
May 18, 2017, 10:26:29 AM5/18/17
to ISO C++ Standard - Future Proposals
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
Reply all
Reply to author
Forward
0 new messages