A strong typedef syntax idea

464 Aufrufe
Direkt zur ersten ungelesenen Nachricht

sval...@gmail.com

ungelesen,
19.12.2016, 06:04:4819.12.16
an ISO C++ Standard - Future Proposals
This is a stub proposal on strong typedefs, i.e. types that work in the exact same way, but allow separate overloading. Other papers and proposals exist, but I've tried a different approach that tries to mimic a more inheritance-like syntax which might be more intuitive. The full text can be found online at https://github.com/Svalorzen/CppCopyProposal.

I'm copying the text below. Thanks in advance for your comments.

Duplication and Extension of Existing Classes
=============================================

Introduction
------------

This document describes a possible approach to duplicate existing functionality
while wrapping it in a new type, without the burden of inheritance and to allow
function overloads on syntactically identical but semantically different types
(also known as *strong typedef*).

The approach taken should be simple to implement and be applicable to existing
code.

Optional sections are to be read as additional ideas that could be further
developed or completely ignored. They are mostly food for thought, but included
for completeness.

Reasons
-------

- Scientific libraries where a type has different behaviors depending on context
  have currently no simple way to indicate the semantic differences. Since a
  `typedef` does not allow multiple overloads on new typedef types - since they
  are still the "old" type - they have to resort to imperfect techniques, such
  as copying, wrapping or inheriting the needed type. Examples: coordinates in a
  plane (rectangular, polar), vectors of double (probabilities, values).
- Easier maintainability of code which is known to be the same, rather than
  being copy-pasted.
- Avoiding misuse of inheritance in order to provide a copy-paste alternative.
  This can result in very deep hierarchies of types which should really not have
  anything to do with each other.
- Enabling users to use an existing and presumably correct type but partially
  extend it with context-specific methods. Examples: search for "`std::vector`
  inheritance" yields many results of users trying to maintain the original
  interface and functionality but add one or two methods.

The functionality should have the following requirements:

- Can be applied to existing code.
- Should limit dependencies between new and old type as much as possible.
- Should allow for partial extensions of the old code.

Alternatives
------------

### Typedef / Using Directive ###

Using a type alias creates an alternative name for a single type. However, this
leaves no space to implement overloads that are context-specific. Nor a type can
be extended in a simple way while keeping the old interface intact.

### Inheritance ###

Inheritance requires redefinition of all constructors, and creates a stricter
dependency between two classes than what is proposed here. Classes may be
converted to a common ancestor even though that is undesired or even dangerous
in case of implicit conversions.

Inheritance may also be unwanted in order to avoid risks linked to polymorphism
and freeing data structures where the base class does not have a virtual
destructor.

### Encapsulation with Manual Exposure of Needed Methods ###

This method obviously requires a great deal of code to be rewritten in order to
wrap every single method that the old class was exposing.

In addition one needs to have intimate knowledge of the original interface in
order to be able to duplicate it correctly. Template methods, rvalue references,
possibly undocumented methods which are required in order to allow the class to
behave in the same way as before. This heightens the bar significantly for many
users, since they may not know correctly how to duplicate an interface and how
to forward parameters to the old interface correctly.

The new code also must be maintained in case the old interface changes.

### Copying the Base Class ###

This can be useful, but requires all code to be duplicated, and thus
significantly increases the burden of maintaining the code. All bugs discovered
in one class must be fixed in the other class too. All new features applied to
one class must be applied to the other too.

### Macro-expansion ###

Macro expansions can be used in order to encode the interface and implementation
of a given class just one time, and used multiple times to produce separate
classes.

This approach is unfortunately not applicable to existing code, and is very hard
to extend if one wants to copy a class but add additional functionality to it.

### Templates ###

Templates produce for each instantiation a separate type. They are unfortunately
not applicable to previously existing code. For new code, they would require the
creation of "fake" template parameters that would need to vary in order to
produce separate types.

In addition, class extension through templates is not possible: variations would
need to be made through specialization, which itself requires copying existing
code.

Previous Work
-------------

Strong typedefs have already been proposed for the C++ language multiple times
([N1706](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1706.pdf),
[N1891](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1891.pdf),
[N3515](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3515.pdf),
[N3741](https://isocpp.org/files/papers/n3741.pdf)). These typedefs are named
*opaque typedefs*, and these papers try to explore and define exactly the
behavior that such typedefs should and would have when used to create new
types. In particular, the keywords `public`, `protected` and `private` are used
in order to create a specific relation with the original type and how is the
new type allowed to be cast back to the original type or be used in its place
during overloads.

This document shares many of the the same principles, for example (quoting from
N3741):

> - Consistent with restrictions imposed on analogous relationships such as
>   base classes underlying derived classes and integer types underlying enums,
>   an underlying type should be (1) complete and (2) not cv-qualified. We also do
>   not require that any enum type, reference type, array type, function type, or
>   pointer-to-member type be allowed as an underlying type.

However, this document tries to propose a possibly more simple approach, where
a new language feature is introduced with the same meaning and functionality as
if the user autonomously implemented a new class him/herself, matching the
original type completely. Thus, it should result for the user more simple to
understand (as it simply matches already the already understood mechanics of
creating a new, unique type from nothing), and no new rules for type conversion
and selection on overloads have to be created.

Syntax
------

### Simple Case ###

Syntax could look something like this:

```cpp
class Base {
    public:
        Base() : x(0) {}
        void foo() { std::cout << "foo " << x << "\n"; }
    private:
        int x;
};

struct Copy : using Base {};

/* Equivalent to

struct Copy {
    public:
        Copy() : x(0) {}
        void foo() { std::cout << "foo " << x << "\n"; }
    private:
        int x;
};

*/
```

One cannot copy a class and inherit at the same time. If such a class is needed
one would need to create it by hand with the desided functionality and
inheriting from the desired classes, as it would be done normally.

All method implementations would be the same. The copied class would inherit
from the same classes its base class inherits from. All constructors would work
in the same way.

### Adding New Functionality ###

Ideally one could specify additional methods, separate from that of Base, to add
upon the existing functionality.

```cpp
struct Base {
    void foo() { std::cout << "foo\n"; }
};

struct Derived : public Base {};

struct Copy : using Base {
    void bar() { std::cout << "bar\n"; }
};

struct CopyDerived : using Derived {};

/* Equivalent to

struct Copy {
    void foo() { std::cout << "foo\n"; }
    void bar() { std::cout << "bar\n"; }
};

struct CopyDerived : public Base {};

*/
```

Only new methods need to be implemented for that class.

#### Interfacing with the Original Class ####

In order to interface with the original class, simple conversion operators can
be added by the user explicitly at-will, in order to obtain the desired
interface. Note that if more types with this kind of compatibility were needed,
one would only need to implement them once, since copying the produced type
would copy the new, more compatible interface with it.

```cpp
struct Base {
    public:
        int x;

    private:
        double y;
};

struct Copy : using Base {
    operator Base() { return Base{x, y}; }
};
```

`reinterpret_cast` may also be used to convert back to the original class,
limited by the tool's already existing rules.

In general the usual rules of `reinterpret_cast` apply to the copied classes
with respect to their general classes, exactly as if the copied class had been
implemented by hand.

### Overloads ###

Duplicating an existing class should allow for new overloads on the new type,
and no ambiguity between the copied class, the old class and other copied
classes.

```cpp
class Position : using std::pair<double, double> {};
class Distance : using std::pair<double, double> {};

Position operator+(const Position & p, const Distance & d) {
    return Position(p.first + d.first, p.second + d.second);
}

Distance operator+(const Distance & lhs, const Distance & rhs) {
    return Distance(lhs.first + rhs.first, lhs.second + rhs.second);
}

// ...

Position p(1, 1);
Distance d(1, 1);

p + d; // OK
d + d; // OK
p + p; // Error
```

### Templated Class Copy ###

The user might want to create a single templatized copy interface, and use it
multiple times. For example, one might want multiple copied classes which can
convert to their original. This could be done as follows:

```cpp
struct A { int x; };

template <typename T>
struct TemplatizedCopy : using T {
    static_assert(std::is_standard_layout<T>::value,
                  "Can't use this with a non-standard-layout class");

    operator T&() { return *reinterpret_cast<T*>(this); }
};

// Could be used either via normal typedefs
using Copy1 = TemplatizedCopy<A>;

// Or via copy, depending on requirements.
struct Copy2 : using TemplatizedCopy<A> {};
```

### Copying Template Classes ###

Since the construct is similar to inheritance, the syntax for creating aliases
of templated classes could be the same:

```cpp
template <typename T>
struct A {};

template <typename T>
struct B : using A<T> {};

B<int> b;
```

The copied class must have the same number or less of template parameters than
the base class. Partial or full specializations of the base class can be allowed:

```cpp
template <typename T, typename U>
struct A {};

template <typename T>
struct B : using A<T, double> {};

B<int> b;
```

When the base class has partial specializations, only those who apply are copied
to the copied class.

```cpp
template <typename T, typename U>
struct A { T t; U u; };

template <typename U>
struct A<double, U> { double y; U u; };

template <typename T>
struct A<T, int> { T t; char z; };

template <typename T>
struct B : using A<T, double> {};

/* Equivalent to

template <typename T>
struct B { T t; double u; };

template <>
struct B<double> { double y; double u; };

*/
```

The copied class can add additional specializations. Or specializations for a
given class can copy another.

```cpp
template <typename T>
struct A { int x; };

struct B { char c; };

template <typename T>
struct C : using A<T> {};

template <>
struct C<double> : using B {};

template <>
struct A<int> : using C<double> {};

/* Equivalent to

template<>
struct A<int> { char c; };

template <typename T>
struct C { int x; };

template <>
struct C<double> { char c; };

*/
```

### Copying Multiple Dependent Classes ###

Copying multiple classes using the simple syntax we have described can be
impossible if those classes depend on one another. This is because each copy
would depend on the originals, rather than on the copied classes. A possible way
to specify such dependencies could be:

```cpp
struct A;

struct B {
    A * a;
};

struct A {
    B b;
};

struct C;

struct D : using B {
    using class C = A;
};

struct C : using A {
    using class D = B;
};

/* Equivalent to

struct C;

struct D {
    C * a;
};

struct C {
    D b;
};

*/
```

`using class` has been used in order to disambiguate it from normal `using`
alias directive. `using class` is only valid when the left hand side has been
defined as a copy of the right hand side.

In case of a template base class using a template second class, one could
specify different copies for certain specializations;

```cpp
template <typename T>
struct A {};

template <typename T>
struct B {
    A<T> a;
};

template <typename T>
struct C : using A<T> {};

```

### Substituting Existing Functionality (Optional) ###

Ideally one may want to use most of an implementation for another class, but
vary a certain number of methods. In this case, if `Copy` contains a member
function that already exists in `Base`, then that implementation is substituted
in `Copy`. This may or may not be allowed for attributes.

```cpp
struct Base {
    void foo() { std::cout << "foo\n"; }
    void bar() { std::cout << "bar\n"; }
};

struct Copy : using Base {
    void foo() { std::cout << "baz\n"; }
};

/* Equivalent to

struct Copy {
    void foo() { std::cout << "baz\n"; }
    void bar() { std::cout << "bar\n"; }
};

*/
```

A side effect of this is that it could allow for some type of "interface", where
some base class could be defined as:

```cpp
struct Base {
    Base() = delete;
    void foo();
    void bar();
};

struct Copy1 : using Base {
    Copy1() = default;
    void baz();
    void foo() = delete;
};

/* Equivalent to

struct Copy1 {
    Copy1() = default;
    void bar();
    void baz();
};

*/

struct Copy2 : using Base {
    Copy2(int);
    void abc();
};

/*

Equivalent to

struct Copy2 {
    Copy2(int);
    void foo();
    void bar();
    void abc();
};

*/
```

This feature could however present problems when the members changed also alter
behavior and/or variable types of non-modified member and non-member functions,
since the new behavior could be either erroneous or ambiguous.

### Copying and Extending Primitive Types (Optional) ###

The same syntax could be used in order to extend primitive types. Using the
extension that allows the modification of the copied types, this could allow for
creation of numeric types where some operations are disabled as needed.

```cpp
struct Id : using int {
    Id operator+(Id, Id) = delete;
    Id operator*(Id, Id) = delete;
    // Non-explicitly deleted operators keep their validity

    // Defining new operators with the old type can allow interoperativity
    Id operator+(Id, int);
    // We can convert the copied type to the old one.
    operator int() { return (*this) * 2; }
};

/* Equivalent to

class Id final {
    public:
        Id operator/(Id lhs, Id rhs) { return Id{lhs.v_ / rhs.v_}; }
        Id operator-(Id lhs, Id rhs) { return Id{lhs.v_ - rhs.v_}; }

        Id operator+(Id, int);
        operator int() { return v_ * 2; }
    private:
        int v_;
};

*/
```

Note that when copying from a primitive types inheritance is forbidden as the
generated copy is `final` (although it is allowed to keep copying the newly
created class).

### STL Traits (Optional) ###

Traits could be included in the standard library in order to determine whether a
class is a copy of another, or if it has been derived from a copy
(copies/inheritances could be nested arbitrarily).

```cpp
struct Base {};

struct Copy : using Base {};

static_assert(std::is_copy<Copy, Base>::value);

struct ChildCopy : public Copy {};

struct CopyChildCopy : using ChildCopy {};

static_assert(std::is_copy_base_of<Base, CopyChildCopy>::value);
```

Compatibility
-------------

As the syntax is new, no old code would be affected.

S.B.

ungelesen,
20.12.2016, 12:18:1820.12.16
an ISO C++ Standard - Future Proposals, sval...@gmail.com
According to your proposal, what should B::Self be in the following example?
struct A {
   
using Self = A;
};
struct B : using A {};


According to your proposal, how should the copy assignement operator of B look like?
struct A {
   
using Self = A;
    A
& operator=(const Self&) { return *this; }
};

struct B : using A {};

Eugenio Bargiacchi

ungelesen,
20.12.2016, 12:42:0720.12.16
an S.B., ISO C++ Standard - Future Proposals
In your first example, B::Self would be B, as all old references to the copied class are changed (as if by preprocessor in some sense) to the new class.

In your second example, B's representation would be exactly the same as this:

struct B {
    using Self = B;
    B& operator=(const Self&) { return *this; }
};

Aside from the fact that if needed one could find out, for example via type_traits, that B is a copy of A. But for practical purpouses the class would be that one.

D. B.

ungelesen,
20.12.2016, 12:50:0120.12.16
an std-pr...@isocpp.org
A quick comment on one thing that stuck out to me while skimming. For now I'll leave the real review to those with more time/experience.

Is reinterpret_cast really the correct tool for this? It seems to me like static_cast would be more appropriate.

Think int vs enum class. The types are not implicitly substitutable but are really the same, or at least sub/supersets... sound familiar? So an explicit static_cast should be safe and preferable, by my understanding.

To me reinterpret_cast signals (screams) 'I probably shouldn't be doing this, but there's no other way, and I promise this pointer/reference was originally (compatible with) what I'm saying it is... honest'.

Eugenio Bargiacchi

ungelesen,
20.12.2016, 13:02:0820.12.16
an ISO C++ Standard - Future Proposals
I'm not sure you can `static_cast` between pointers of unrelated classes (I think not actually). `reinterpret_cast` is also very limited in what it can do in this case: I'm not 100% sure on the details, but in general I think it is allowed only when the two classes are `standard_layout` (which has a bunch of restrictions).

In any case, please note that `reinterpret_cast` is used as an example, and it would still keep all already existing rules with no modifications, as this proposal does not even try to go there. How an user would like to use the feature and define additional methods is his/her concern. In terms of the proposal, all cpp features work as if the user had implemented the new class by hand, so no new rules have to be introduced.

--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/gkJUVnL-Fmg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposals+unsubscribe@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/CACGiwhHGNADMd0orere%3DYZ2_y7UB8Y0MPU5p25d1fE8i2WKrpA%40mail.gmail.com.

Bengt Gustafsson

ungelesen,
20.12.2016, 18:58:3020.12.16
an ISO C++ Standard - Future Proposals, sval...@gmail.com
I like the basic idea but I think it may get too complicated if replacements and extensions are allowed. Isn't this what we have inheritance for?

For the part about interdependent classes which need to get copied I think your example violates your rule as these two classes are dependant in both directions, the rule needs to be more lenient when it comes to ordering of declarations.

I think that the most common case of interdependant groups of classes to get copies will be parent and child classes. Would your idea with a nested "using class C = A" cover that case to? I think an example of how this would look would be warranted.

I think that making strong typedefs of primitive types is maybe the most important case. It does seem very odd to use the keyword 'struct' to achieve this... then again, without thinking too deeply about it I could find no compelling reason that you would not be able to inherit from primitive types. This would make the struct keyword more appropriate as you can inherit a struct from a primitive type and redefine its functions (which of those are to be considered as free or member functions is another issue).

Finally, if you add copy ctors and cast operators to/from the original types, doesn't this defy the purpose of the "strong" in the typedef? Or do you mean that such cast operators are implicitly explicit? My take on this would be that there should probably be explicit conversion possibilities between original and copy unless maybe if you = delete them.

Klaim - Joël Lamotte

ungelesen,
21.12.2016, 04:03:0821.12.16
an std-pr...@isocpp.org
Hi,
This is an interesting proposal.
May I suggest you change your "copy" word usage for something else or more detailed?
"Copy" operation already have a meaning when manipulating objects,
"Template" is obviously already taken too,

So maybe something like "code-copy" or "type-copy" or something like that would fit best.

My main question while reading this proposal:
What happen if the original type have a hidden implementation?
That is:


    class A {
        class Impl; 
        Impl* pimpl; 
    public:
        A();.         
        Impl* fork();
     };
 
     class B : using C {};

Assuming A's functions are defined in another translation unit which is already compiled (for example a shared library file):

1. Is this allowed by your proposal?
2. If yes, what is the type of B::Impl? A typedef of A::Impl? 
3. Do you propose that nested types be implicitly type-copied too?

Then:

4. I'm also concerned by symbol export/import in these cases. Even if not defined by the standard, if the feature have strong limitations when importing/exporting symbols, it might become a no-go proposal.


5. What about class specifiers like alignof/alignas? Are they maintained? How would one remove them?

6. Would there be a way to remove types and typedefs from the original type?
Just wondering, not a feature request.

Joël Lamotte.

 
 




Sent from mobile.

You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.

To post to this group, send email to std-pr...@isocpp.org.

Eugenio Bargiacchi

ungelesen,
21.12.2016, 07:41:3121.12.16
an ISO C++ Standard - Future Proposals
Bengt, I understand it may get a bit complicated. Replacements are actually a feature that I proposed just to get it considered. I believe the feature should allow to add new methods/features to a class; substituting/deleting existing functionality is more complicated but it could have its use cases and would allow to make better use of existing class implementations, so that's why I added it for considerations.

About the part on interdependent classes I have written that the classes should be "defined" as copies of their aliased equivalent. Not sure if the compiler can handle it in one pass, or if there is a better way to state that requirement.

I believe that making strong typedefs in general is important, and not only for primitive types. Sure making them on primitive types only would still be an improvement, but there are many compelling use-cases for also making strong typedefs of classes, for example in scientific fields.

I have added in my examples copy constructors and the like since they are considered heavily in the already existing proposals, and they state that requirements were gathered after extensive discussions with many potential users. It's definitely not a requirement to add them, I just wanted to show how it could be done since it could be needed.

Joël, about copy I feel the same way. I'll try to work out a nicer terminology.

About internal classes, this is a very good question. Actually I realized yesterday that another thing which could be hard to define correctly would be how to treat `friend` declarations. A relatively simple solution would be that all names that depend on the original name are copied, while those that don't are not ported over (for example friend method declarations/definitions).

So in your example A::Impl would be copied into B::Impl. However, in this example:

void bar() {}

struct A {
    friend int foo(A) { return 0; }
    friend void bar();
};

struct B : using A {};

Nothing would be copied, since the functions `foo` and `bar` do not depend directly on the original type. If needed, a different `int foo(B)` can be separately implemented by the copier. It's not exactly intuitive, but I believe it would be better, otherwise copying everything defined "internally" could lead to recursive copying of many things which one would not expect copied.

About symbols, personally I think they should work as if the user had written the new class by hand, if possible. This should keep the rules relatively simple, as a user can always create a new copy of a class by hand (one cannot do inheritance by hand, for example, as the language gives guarantees about it not achievable by writing a new class from scratch).

I would say alignof/as would be kept. The same for existing typedefs. The idea here is to offer a certain amount of customizations of classes that are copied, but not too much - otherwise it would be best for the user to just create the new type by hand. The line must be drawn somewhere, and I'd prefer the feature to be simple rather than having to define how to take apart an existing type. Ideally the feature would be more constructive than destructive, as in first define types, and each copy adds stuff, rather than start with a big type and remove things from it. Having the possibility to remove things however gives space to make better use of existing classes, and that's why I included it.

Best,
Eugenio

Tom Honermann

ungelesen,
21.12.2016, 08:40:1521.12.16
an std-pr...@isocpp.org
On 12/19/2016 06:04 AM, sval...@gmail.com wrote:
struct Base {};

struct Copy : using Base {};

This syntax was proposed in P0352R0 [1] as part of an alternate operator.() feature and received favorable review from EWG.  I find the semantics proposed in P0352R0 to be a better use of this syntax.

Tom.

[1]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0352r0.pdf

Eugenio Bargiacchi

ungelesen,
21.12.2016, 09:21:0521.12.16
an ISO C++ Standard - Future Proposals
I didn't know of that paper. I suppose in case the syntax keyword could be changed, perhaps `struct Copy : default Base {}`? Or some alternative punctuation other than a single ':' could be used between the two classes.

--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/gkJUVnL-Fmg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.

Klaim - Joël Lamotte

ungelesen,
21.12.2016, 13:12:4121.12.16
an std-pr...@isocpp.org

On 21 December 2016 at 13:41, Eugenio Bargiacchi <sval...@gmail.com> wrote:
Joël, about copy I feel the same way. I'll try to work out a nicer terminology.

Maybe use "clone-type" or something like that?

Anyway yeah even for functions overloading there is always the same problem with any kind of strong typedef:

class A {};
class B : using/cloning A {};

void foo(A& a);

Here your proposal suggests that foo() cannot be used with B.
But maybe we want that to be possible?
Add a way to clone free functions too? How about allowing all operations from A to B?

Also isn't there a risk of member access?

class A { 
    int hidden_value; 
  public:
      A();
};

class B : using/cloning A
{
public:

   int cheat() { return hidden_value; }
};


This looks necessary but a bit problematic also. Apparently Modules wouldn't change a thing as long
as type members are all considered exported.

Joël Lamotte




Eugenio Bargiacchi

ungelesen,
21.12.2016, 13:41:3921.12.16
an ISO C++ Standard - Future Proposals
I suppose that if we want to use foo with B, one could include an operator A() to B. In any case, the point in making a strong typedef is that you are indeed making a new type. If it was completely compatible with the old one, there would be little point, right?

In any case, I believe this case is what templates are for. They are there to create functions that interact with object with the same interface, so in this case one would want foo to be a templated function. Copy + Template would be the equivalent of Inheritance + Interfaces. Maybe a way to clone functions could be added in order to interface with old code, but I'm not sure whether it should be in the scope of this proposal or be separated in another one.

For member access, keep in mind that you are making a new class. The risk is exactly the same when you create a new class and expose members which should be private. In any case, this won't make you able to access A's private members, only B's, which is different since it is another class entirely. Why then you would want to create a new class which ignores access specifiers is another problem, but one that exists independently of this proposal.

--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/gkJUVnL-Fmg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.

D. B.

ungelesen,
21.12.2016, 13:57:5921.12.16
an std-pr...@isocpp.org
On Wed, Dec 21, 2016 at 6:12 PM, Klaim - Joël Lamotte <mjk...@gmail.com> wrote:

On 21 December 2016 at 13:41, Eugenio Bargiacchi <sval...@gmail.com> wrote:
Joël, about copy I feel the same way. I'll try to work out a nicer terminology.

Maybe use "clone-type" or something like that?

Anyway yeah even for functions overloading there is always the same problem with any kind of strong typedef:

class A {};
class B : using/cloning A {};

void foo(A& a);

Here your proposal suggests that foo() cannot be used with B.
But maybe we want that to be possible?

Isn't like the entire point of strong typedefs that we don't want this kind of substitutability to be possible with them?

 
Add a way to clone free functions too? How about allowing all operations from A to B?

What does it mean to clone a function, and how does this relate to the concept of strong typedefs?

 
Also isn't there a risk of member access?

class A { 
    int hidden_value; 
  public:
      A();
};

class B : using/cloning A
{
public:

   int cheat() { return hidden_value; }
};

How would this be a problem? The point is to copy a class, so of course its private members should be available.

At this point, I don't like the inheritance-like syntax much, but that's separate, and conceptually, private members should be available.
 

This looks necessary but a bit problematic also. Apparently Modules wouldn't change a thing as long
as type members are all considered exported.

Class members must be exported, and I don't see how that will or could change with modules; it would be such a fundamental  change to the language that we might as well rename it at the same time.

Nicol Bolas

ungelesen,
21.12.2016, 14:10:1321.12.16
an ISO C++ Standard - Future Proposals
On Wednesday, December 21, 2016 at 1:57:59 PM UTC-5, D. B. wrote:
On Wed, Dec 21, 2016 at 6:12 PM, Klaim - Joël Lamotte <mjk...@gmail.com> wrote:

On 21 December 2016 at 13:41, Eugenio Bargiacchi <sval...@gmail.com> wrote:
Joël, about copy I feel the same way. I'll try to work out a nicer terminology.

Maybe use "clone-type" or something like that?

Anyway yeah even for functions overloading there is always the same problem with any kind of strong typedef:

class A {};
class B : using/cloning A {};

void foo(A& a);

Here your proposal suggests that foo() cannot be used with B.
But maybe we want that to be possible?

Isn't like the entire point of strong typedefs that we don't want this kind of substitutability to be possible with them?

Sometimes it is. And sometimes it isn't. Sometimes a non-member function is just a regular function that takes `A`. And sometimes, it is genuinely part of `A`'s interface. Operators being one of the most important such functions, but there are others.

This is why strong typedef proposals are so complex. To pick either answer for all functions inhibits the utility of the feature in some way.

Klaim - Joël Lamotte

ungelesen,
21.12.2016, 14:20:2521.12.16
an std-pr...@isocpp.org
On 21 December 2016 at 19:41, Eugenio Bargiacchi <sval...@gmail.com> wrote:
I suppose that if we want to use foo with B, one could include an operator A() to B.

Not necessarily:

class A
{
   std::mutex my_mutex; // not copyable or moveable, not part of the interface
public:

    A(); 
};

class B : using/cloning A
{};

I expect this to work but not that B could be convertible to A without a static cast.
 
In any case, the point in making a strong typedef is that you are indeed making a new type. If it was completely compatible with the old one, there would be little point, right?


I agree but in the same time, most types you need to clone have free functions as part of their interfaces.
This is a classic problem with strong typedefs.
 
In any case, I believe this case is what templates are for. They are there to create functions that interact with object with the same interface, so in this case one would want foo to be a templated function.

It would be nice but you are talking about a function you might not have control over with.
It can be solved with a cast though.
 
Copy + Template would be the equivalent of Inheritance + Interfaces. Maybe a way to clone functions could be added in order to interface with old code, but I'm not sure whether it should be in the scope of this proposal or be separated in another one.


Maybe not, maybe begin with what you have to get initial feedback.
I watched recently Stroustrup explain that in the end he is not for strong typedefs feaures anymore, because just create a type and go with it.
So I believe that any attempt to put the feature back in discussion will have to pass a higher barrier of interest, even if Stroustrup is "only"
one vote.
 
For member access, keep in mind that you are making a new class. The risk is exactly the same when you create a new class and expose members which should be private. In any case, this won't make you able to access A's private members, only B's, which is different since it is another class entirely. Why then you would want to create a new class which ignores access specifiers is another problem, but one that exists independently of this proposal.


Ah yes, I understand.

 

--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.

To post to this group, send email to std-pr...@isocpp.org.

Klaim - Joël Lamotte

ungelesen,
21.12.2016, 14:27:5221.12.16
an std-pr...@isocpp.org
On 21 December 2016 at 19:57, D. B. <db0...@gmail.com> wrote:


On Wed, Dec 21, 2016 at 6:12 PM, Klaim - Joël Lamotte <mjk...@gmail.com> wrote:

On 21 December 2016 at 13:41, Eugenio Bargiacchi <sval...@gmail.com> wrote:
Joël, about copy I feel the same way. I'll try to work out a nicer terminology.

Maybe use "clone-type" or something like that?

Anyway yeah even for functions overloading there is always the same problem with any kind of strong typedef:

class A {};
class B : using/cloning A {};

void foo(A& a);

Here your proposal suggests that foo() cannot be used with B.
But maybe we want that to be possible?

Isn't like the entire point of strong typedefs that we don't want this kind of substitutability to be possible with them?


As Nicol Bolas said.
 
 
Add a way to clone free functions too? How about allowing all operations from A to B?

What does it mean to clone a function, and how does this relate to the concept of strong typedefs?


Cloning a function:

A foo(A&);

The cloned function would be equivalent to

B foo(B&b ) { B result = some_cast foo( some_cast<A&>(b) ); return result; }

But the replaced types would need to be clone relatives.

Also it wouldn't be very interesting if the syntax wouldn't allow to do that with "all" the functions
relaetd to the original type.

But it's all speculation and ideas, I didn't think much more than that.
 
 
Also isn't there a risk of member access?

class A { 
    int hidden_value; 
  public:
      A();
};

class B : using/cloning A
{
public:

   int cheat() { return hidden_value; }
};

How would this be a problem? The point is to copy a class, so of course its private members should be available.

At this point, I don't like the inheritance-like syntax much, but that's separate, and conceptually, private members should be available.
 

Indeed.
 

This looks necessary but a bit problematic also. Apparently Modules wouldn't change a thing as long
as type members are all considered exported.

Class members must be exported, and I don't see how that will or could change with modules;

It was not proposed at first and my understanding is that the main author of Modules believe it is possible to not export the private members.
(if my memory from discussions is correct)
 
it would be such a fundamental  change to the language that we might as well rename it at the same time.


It wouldn't change more in the language than when you use code that come with a header stripped from details and a binary to link with.
But yeah it would change things from the source usage pov.
 

--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.

Eugenio Bargiacchi

ungelesen,
21.12.2016, 14:28:1321.12.16
an ISO C++ Standard - Future Proposals
I agree but in the same time, most types you need to clone have free functions as part of their interfaces.
This is a classic problem with strong typedefs.

I agree, I have though a while about this while writing this proposal. I thought about giving the possibility of "cloning" a function (as in, creating an equivalent version line by line with just all instances of the copied type replaced by the new type) within the scope of the copied class.

But then I realized that if that was allowed, at that point it might as well be allowed for all classes, for all functions. And so it would become a feature on its own, or at least a very big scope increase. Thus I removed it from the proposal.

If you feel that such a feature cannot be reasonably excluded from a complete proposal about strong typing, I'll try to think about a way to extend the syntax for functions, or even better find something which looks equally good for both classes and functions.

My first guess was that maybe trying to keep the proposal as limited in scope as possible would be best, so that it would be easier what could go wrong. Even a small step towards strong typing would be better than nothing, I guess. Even if some classes can be harder to copy/clone if they are mostly defined by their free-functions ecosystem, still there's many for which this is not true.

But again, if you feel it would be best to think about this and try to add it, I'll definitely try to.

Klaim - Joël Lamotte

ungelesen,
21.12.2016, 14:31:3721.12.16
an std-pr...@isocpp.org
On 21 December 2016 at 20:28, Eugenio Bargiacchi <sval...@gmail.com> wrote:
I agree but in the same time, most types you need to clone have free functions as part of their interfaces.
This is a classic problem with strong typedefs.

I agree, I have though a while about this while writing this proposal. I thought about giving the possibility of "cloning" a function (as in, creating an equivalent version line by line with just all instances of the copied type replaced by the new type) within the scope of the copied class.

But then I realized that if that was allowed, at that point it might as well be allowed for all classes, for all functions. And so it would become a feature on its own, or at least a very big scope increase. Thus I removed it from the proposal.

If you feel that such a feature cannot be reasonably excluded from a complete proposal about strong typing, I'll try to think about a way to extend the syntax for functions, or even better find something which looks equally good for both classes and functions.


My first guess was that maybe trying to keep the proposal as limited in scope as possible would be best, so that it would be easier what could go wrong. Even a small step towards strong typing would be better than nothing, I guess. Even if some classes can be harder to copy/clone if they are mostly defined by their free-functions ecosystem, still there's many for which this is not true.

But again, if you feel it would be best to think about this and try to add it, I'll definitely try to.

I agree, 
I think it would be ok to just mention the possibility in the proposal and write a separate proposal.
I don't have practical experience with such a feature so maybe it's not even important (I think it can be, but maybe the effort with already
standardized code would not be that verbose to write).
  

Vicente J. Botet Escriba

ungelesen,
31.12.2016, 06:15:0031.12.16
an std-pr...@isocpp.org
Le 19/12/2016 à 12:04, sval...@gmail.com a écrit :
This is a stub proposal on strong typedefs, i.e. types that work in the exact same way, but allow separate overloading. Other papers and proposals exist, but I've tried a different approach that tries to mimic a more inheritance-like syntax which might be more intuitive. The full text can be found online at https://github.com/Svalorzen/CppCopyProposal.

I'm copying the text below. Thanks in advance for your comments.
Hi,

your proposal has the merit to propose a different approach. IIUC, you approach consists in introducing/duplicating all members (type, data, functions, constructors/destructors) from the underlying class, but no the friends.
p0109r0 introduce only conversions to/from the underlying type.

IIUC, every occurrence of the base type in the definition of the base type is replaced by the new type. This is one of the options described in Opaque proposal (see The return type issue). Why the other alternatives have less sens?



Duplication and Extension of Existing Classes
=============================================

Introduction
------------

This document describes a possible approach to duplicate existing functionality
while wrapping it in a new type, without the burden of inheritance and to allow
function overloads on syntactically identical but semantically different types
(also known as *strong typedef*).

The word duplicating and wrapping don't match. The proposed approach doesn't wraps the underlying type, except maybe for builtin types.
I believe the proposal needs to add some concrete examples as use cases


Alternatives
------------

### Typedef / Using Directive ###

Using a type alias creates an alternative name for a single type. However, this
leaves no space to implement overloads that are context-specific. Nor a type can
be extended in a simple way while keeping the old interface intact.

### Inheritance ###

Inheritance requires redefinition of all constructors, and creates a stricter
dependency between two classes than what is proposed here. Classes may be
converted to a common ancestor even though that is undesired or even dangerous
in case of implicit conversions.

Inheritance may also be unwanted in order to avoid risks linked to polymorphism
and freeing data structures where the base class does not have a virtual
destructor.

### Encapsulation with Manual Exposure of Needed Methods ###

This method obviously requires a great deal of code to be rewritten in order to
wrap every single method that the old class was exposing.

In addition one needs to have intimate knowledge of the original interface in
order to be able to duplicate it correctly. Template methods, rvalue references,
possibly undocumented methods which are required in order to allow the class to
behave in the same way as before. This heightens the bar significantly for many
users, since they may not know correctly how to duplicate an interface and how
to forward parameters to the old interface correctly.

The new code also must be maintained in case the old interface changes.
If the base class change, the strong types depending on it could need maintenance also, isn't it?


### Copying the Base Class ###

This can be useful, but requires all code to be duplicated, and thus
significantly increases the burden of maintaining the code. All bugs discovered
in one class must be fixed in the other class too. All new features applied to
one class must be applied to the other too.

### Macro-expansion ###

Macro expansions can be used in order to encode the interface and implementation
of a given class just one time, and used multiple times to produce separate
classes.

This approach is unfortunately not applicable to existing code, and is very hard
to extend if one wants to copy a class but add additional functionality to it.

### Templates ###

Templates produce for each instantiation a separate type. They are unfortunately
not applicable to previously existing code. For new code, they would require the
creation of "fake" template parameters that would need to vary in order to
produce separate types.

In addition, class extension through templates is not possible: variations would
need to be made through specialization, which itself requires copying existing
code.

In addition to these basic techniques, there are other library solution, as e.g. using CRTP that might merit to be mentioned.
See [TBoost.Opaque] for a possible implementation.

Previous Work
-------------

Strong typedefs have already been proposed for the C++ language multiple times
([N1706](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1706.pdf),
[N1891](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1891.pdf),
[N3515](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3515.pdf),
[N3741](https://isocpp.org/files/papers/n3741.pdf)). These typedefs are named
*opaque typedefs*, and these papers try to explore and define exactly the
behavior that such typedefs should and would have when used to create new
types. In particular, the keywords `public`, `protected` and `private` are used
in order to create a specific relation with the original type and how is the
new type allowed to be cast back to the original type or be used in its place
during overloads.


This document shares many of the the same principles, for example (quoting from
N3741):

> - Consistent with restrictions imposed on analogous relationships such as
>   base classes underlying derived classes and integer types underlying enums,
>   an underlying type should be (1) complete and (2) not cv-qualified. We also do
>   not require that any enum type, reference type, array type, function type, or
>   pointer-to-member type be allowed as an underlying type.

However, this document tries to propose a possibly more simple approach, where
a new language feature is introduced with the same meaning and functionality as
if the user autonomously implemented a new class him/herself, matching the
original type completely. Thus, it should result for the user more simple to
understand (as it simply matches already the already understood mechanics of
creating a new, unique type from nothing), and no new rules for type conversion
and selection on overloads have to be created.
What is wrong for you with p0109r0 approach?
What we could have with your approach that we cannot have with p0109r0?
What we could have with p0109r0 that we cannot have with your approach?

IIUC, p0109r0 opaque types don't introduce any operations by default other than conversions and your proposal introduce all members but no friends




Syntax
------

### Simple Case ###

Syntax could look something like this:

```cpp
class Base {
    public:
        Base() : x(0) {}
        void foo() { std::cout << "foo " << x << "\n"; }
    private:
        int x;
};

struct Copy : using Base {};

is this almost equivalent to p0109r0

    using Copy : private Base {
        using Base::foo; // if this was allowed in p0109r0
I find that conversions to/from the underlying type must be covered by the proposal. If we change the Base type we don't want to be forced to redefine the conversion operator.
Maybe we need some kind of default conversion implementation

    operator Base() = default;

or

    explicit operator Base() = default;
`reinterpret_cast` may also be used to convert back to the original class,
limited by the tool's already existing rules.
Agree reinterpret_cast should be allowed.

In general the usual rules of `reinterpret_cast` apply to the copied classes
with respect to their general classes, exactly as if the copied class had been
implemented by hand.

<snip>


### Copying Template Classes ###

Since the construct is similar to inheritance, the syntax for creating aliases
of templated classes could be the same:

```cpp
template <typename T>
struct A {};

template <typename T>
struct B : using A<T> {};

B<int> b;
```

The copied class must have the same number or less of template parameters than
the base class.
Why do you introduce this restriction?
<snip>


### Copying Multiple Dependent Classes ###

Copying multiple classes using the simple syntax we have described can be
impossible if those classes depend on one another. This is because each copy
would depend on the originals, rather than on the copied classes. A possible way
to specify such dependencies could be:

```cpp
struct A;

struct B {
    A * a;
};

struct A {
    B b;
};

struct C;

struct D : using B {
    using class C = A; // **

};

struct C : using A {
    using class D = B;
};

/* Equivalent to

struct C;

struct D {
    C * a;
};

struct C {
    D b;
};

*/
```

`using class` has been used in order to disambiguate it from normal `using`
alias directive. `using class` is only valid when the left hand side has been
defined as a copy of the right hand side.
In ** above C has not yet defined as a copy


In case of a template base class using a template second class, one could
specify different copies for certain specializations;

```cpp
template <typename T>
struct A {};

template <typename T>
struct B {
    A<T> a;
};

template <typename T>
struct C : using A<T> {};

```

I believe this interdependent case introduce a lot of complexity. We would need a good use case to introduce it on the standard.

### Substituting Existing Functionality (Optional) ###

I believe that this cannot be optional as in a lot of cases we need to modify/restrict the base type interface.
Could you give a concrete example of this kind of problems?


### Copying and Extending Primitive Types (Optional) ###

The same syntax could be used in order to extend primitive types. Using the
extension that allows the modification of the copied types, this could allow for
creation of numeric types where some operations are disabled as needed.

Note the missing friend below
```cpp
struct Id : using int {
    friend Id operator+(Id, Id) = delete;
   
friend Id operator*(Id, Id) = delete;

    // Non-explicitly deleted operators keep their validity

While this is inline with your approach, this merits some explanations as the operators are no members. Shouldn't the operators of UDT classes meritt to be inherited as well?

    // Defining new operators with the old type can allow interoperativity
    friend Id operator+(Id, int);

    // We can convert the copied type to the old one.
    operator int() { return (*this) * 2; }
};

I believed that there where no implicit conversions on your proposals. How (*this) is converted to an int?

What would be the result type of x below?

Id id(1);
auto x = +id;

I suspect that it would be Id.


/* Equivalent to

class Id final {
    public:
        Id operator/(Id lhs, Id rhs) { return Id{lhs.v_ / rhs.v_}; }
        Id operator-(Id lhs, Id rhs) { return Id{lhs.v_ - rhs.v_}; }

        Id operator+(Id, int);
        operator int() { return v_ * 2; }
    private:
        int v_;
};

*/
```

Note that when copying from a primitive types inheritance is forbidden as the
generated copy is `final` (although it is allowed to keep copying the newly
created class).
Why this restriction is needed or desirable.


### STL Traits (Optional) ###

Traits could be included in the standard library in order to determine whether a
class is a copy of another, or if it has been derived from a copy
(copies/inheritances could be nested arbitrarily).

```cpp
struct Base {};

struct Copy : using Base {};

static_assert(std::is_copy<Copy, Base>::value);

struct ChildCopy : public Copy {};

struct CopyChildCopy : using ChildCopy {};

static_assert(std::is_copy_base_of<Base, CopyChildCopy>::value);
```

Example of where this can be useful welcome.

How other type traits behave for the copied class (see p0109r0)?


In summary, I believe that

Vicente

[p0109r0] http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0109r0.pdf

[TBoost.Opaque] https://htmlpreview.github.io/?https://github.com/viboes/opaque/blob/master/libs/opaque/doc/html/index.html

Eugenio Bargiacchi

ungelesen,
31.12.2016, 09:57:4431.12.16
an ISO C++ Standard - Future Proposals
Dear Vincente,

Thank you for your very in-depth review. I've updated the proposal using already received comments from this thread, so some things have changed, but it's still alright to receive comments on this version. I'll explain below how it has changed when answering to your comments.


IIUC, every occurrence of the base type in the definition of the base type is replaced by the new type. This is one of the options described in Opaque proposal (see The return type issue). Why the other alternatives have less sens?

There are many differences between my proposal and the Opaque proposal. I believe that the main ones that this proposal brings are:

- Where possible, do not introduce new meanings or rules to the language. The type-copied class should behave as closely as possible to a class the user has implemented by hand. This should make very easy to understand how the feature works, without the need to grasp many new concepts.
- I have removed the option to modify and remove existing functionality from the class that is being copied. While I believe that this can be useful, it introduces too much complexity in my opinion now. If this is allowed, you basically have to create a system where you are allowed to completely rewrite an existing class starting from another, since you may want to copy or remove or change anything that was previously present. This I believe can both make the proposal unnecessary complicated, and can make the code very hard to follow, as at each new strong-typedef step (since a type could be copied, and the copy copied again and so on) anything could happen. I now believe that an incremental-only strategy (similar to inheritance) can still be both useful and sufficient for most cases. Where it is not, simple implementations by hand of basic functionality, extended then via the type-copy mechanism should result in clear, reusable code which still requires little maintenance.

The word duplicating and wrapping don't match. The proposed approach doesn't wraps the underlying type, except maybe for builtin types.

Right, I'll fix it, thanks.


I believe the proposal needs to add some concrete examples as use cases

This makes sense, I'll worn ok an additional section where I try to show some concrete examples.

If the base class change, the strong types depending on it could need maintenance also, isn't it?

True, however wrapper methods have to be fixed 100% of the time, while a type-copied class may not need this. It's the same as if one modified a base class in inheritance - you don't have to necessarily update all the code that depends on it right away.


In addition to these basic techniques, there are other library solution, as e.g. using CRTP that might merit to be mentioned.
See [TBoost.Opaque] for a possible implementation.

I'll add a mention to CRTP. I'll give a look at the boost link, thanks!
Thanks, I must have missed it.

What is wrong for you with p0109r0 approach?
What we could have with your approach that we cannot have with p0109r0?
What we could have with p0109r0 that we cannot have with your approach?

IIUC, p0109r0 opaque types don't introduce any operations by default other than conversions and your proposal introduce all members but no friends

Personally, I don't think there's anything "wrong" with p0109r0. The idea for this proposal came to me before I knew the alternatives, but since C++ still does not have strong typedefs I thought I might as well propose an alternative approach, which could possibly garner more interest.

I believe my approach is simpler to read and more intuitive - especially when creating strong aliases of very complex types, but that can definitely be bias. It can be applied to hierarchies of classes, since it allows type-copying interdependent classes, which I believe is very important. It is very easy to use with templates and specializations, which potentially gives it a lot of power and very many potential uses. It is easily composable, as in copying a copy is very easy and indeed expected in my view for the feature.

With p0109r0 there's more flexibility to alter a type. The additional flexibility allows it to take on more tasks, as in type-copying friends, since default ways to convert to the original types exist. It is designed for primitive types first, while I have actually removed them in my last iteration for a number of reasons (see below). The additional flexibility however comes at a cost, as you mention the return type issue, which my proposal does not have.

I don't introduce friends mostly because a feature to create type-aliases of methods does not currently exist, and I don't believe it is wise to add it to the scope of this proposal since it would be another very large change. If such a feature existed, however, adding friends to a type-copied class in my proposal would be trivial, as simply strong typedefs of the needed functions could be created on the fly. The same could also be done for non-member functions if needed, but how that would work I have not thought about yet.


I find that conversions to/from the underlying type must be covered by the proposal. If we change the Base type we don't want to be forced to redefine the conversion operator.
Maybe we need some kind of default conversion implementation
    operator Base() = default;
or
    explicit operator Base() = default;

I like this. I didn't add it to keep the number of features as low as reasonably possible, but this is simple enough. This could also be used recursively, as in a copy of a copy could define a conversion operator to both the first copy and the original in this way. However, this default way to define a conversion operator would be disabled if the copy has added new attributes with respect to its original class.

Why do you introduce this restriction? (on template parameter numbers)

No actually, you're right. I should lift it. I thought initially that there wouldn't be any reason to add more template parameters, as they would only be needed to satisfy the original class. But this is definitely wrong. I'll change it, thanks.

In ** above C has not yet defined as a copy

Ill explain how I believe this example should work. Since C has been declared but not defined when parsing D, the compiler will be allowed to assume that C is going to be defined later as a type-copy of A. If that does not happen, then the compiler will give an error, either at the definition of C or of D, explaining that D assumed that C would have been a type-copy while it was not. If C has added new attributes to its declaration though D's definition would need to take the new size of C into account though. Not sure if this can be done or if it should result in an error.

I believe this interdependent case introduce a lot of complexity. We would need a good use case to introduce it on the standard.

A very simple example would be copying of inherited classes, both base and derived. Without a way to do this that just cannot be done. This can be important if one does not want that the derived type and its type-copy are allowed to be converted to the same base. This is a strong reason, I believe, otherwise strong-typing in that case just lost some power in keeping original and type-copy apart.


I believe that this cannot be optional as in a lot of cases we need to modify/restrict the base type interface.

I have actually removed this in my newest revision. I believe that ground-up building of types is the better way to go (as is normally done in inheritance). Having the power to completely alter the original type, possibly to a point where there was not even much in common between the original and the type-copy, is not worth it. This is a personal opinion, but I believe doing so can prevent some very complex and ugly code smells, at a not incredible cost.

Could you give a concrete example of this kind of problems?

Suppose

struct A {
     int foo(int x) { return x * 2; }
     double bar(double x) { return foo(x) * 4.0; }
};

struct B : using A {
    int foo(int) = delete;
    // bar cannot compile anymore
};

struct C : using A {
    double foo(int x) { return x * 2; }
    // ambiguous definition
};

struct D : using A {
    double foo(double x) { return x * 3; }
    // Changes meaning of bar underhandedly with no warning
};

I suppose even more dangerous and complex cases could be devised.

While this is inline with your approach, this merits some explanations as the operators are no members. Shouldn't the operators of UDT classes meritt to be inherited as well?

I've removed primitive type support also for this reason. They are not currently treated as classes by C++, and so I think I shouldn't either. Instead my approach is now constructive. If one needed aliases for primitive types, one could create a single header of wrappers in the form

template <typename T>
class SimpleWrapper {
    public:
        SimpleWrapper(T t) : t_(t) {}

    private:
        T t_;
};

template <typename T>
struct SimpleWrapperWithSum : using SimpleWrapper<T> {
    SimpleWrapperWithSum operator+(const SimpleWrapperWithSum & other) { return t_ + other.t_; }
};

And so on. It would only be needed once, and then users could simply copy the versions with the operators they need to use. It's not incredibly pretty and it does have some limitations, but it works, and in any case even in p0109r0 one would need to remove all unneeded operators, so work would need to be done anyway.

I believed that there where no implicit conversions on your proposals. How (*this) is converted to an int?

What would be the result type of x below?

Id id(1);
auto x = +id;

I suspect that it would be Id.
Yeah, this was a weird syntax, I thought it could be a simple way to represent conversion to the underlying primitive type.

The type of the operation would be Id, yes. I believe that to be the only intuitive result that makes sense, unless an explicit operator+ that does a conversion has been defined somewhere. If a cast is needed, one needs to cast. I don't see a reason to change that for strong typedefs, otherwise the language would be inconsistent IMO.

Why this restriction is needed or desirable. (w.r.t. final in type-copies of primitive types)

It is not. But I believe that if one needs to do something, it has to be in line with the rest of the language. If a strong typedef of a primitive type is needed, it would still need to follow the rules of primitive types. As primitive types are not inheritable, I believe neither should the strong typedefs. Maybe this is a wrong position, I don't know (since primitive typedefs are not inheritable due to C compatibilities IIUC), but I believe that having consistency is still useful for a proposal. Otherwise one needs to always learn a million exceptions to each rule, and that I don't like, where it can be avoided. In any case, these are my opinions, but if there is strong consensus to change how the proposal works I have no problem in modifying it.


Example of where this can be useful welcome.

I must admit I didn't really think about this, I just thought they could be nice to have. Maybe they would be useless. I just considered that sometimes the ingenuity of how people use tools always seem to surprise, so why not. Maybe in order to SFINAE the generation of conversion operators to classes, given that they are copies of the original? Something like

struct Copy : using A {
     template <typename T, /* enable_if_t<is_copy_of_v<T, A>>> */>
     operator T = default;
};

How other type traits behave for the copied class (see p0109r0)?

All other type traits behave as if the class had been implemented by hand, and is separate from the original. so is_same would return false, for example. It seems that p0109r0 thinks the same way. The only difference is that sizeof may be different, since in my proposal one could add additional attributes to the type-copied class. (There's no examples in the old version, I've added them on GitHub though).

Thanks again for your feedback.

Best,
Eugenio Bargiacchi

--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/gkJUVnL-Fmg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.

Vicente J. Botet Escriba

ungelesen,
31.12.2016, 12:15:3131.12.16
an std-pr...@isocpp.org
Le 31/12/2016 à 15:57, Eugenio Bargiacchi a écrit :
Dear Vincente,

Thank you for your very in-depth review. I've updated the proposal using already received comments from this thread, so some things have changed, but it's still alright to receive comments on this version. I'll explain below how it has changed when answering to your comments.

IIUC, every occurrence of the base type in the definition of the base type is replaced by the new type. This is one of the options described in Opaque proposal (see The return type issue). Why the other alternatives have less sens?

There are many differences between my proposal and the Opaque proposal. I believe that the main ones that this proposal brings are:

- Where possible, do not introduce new meanings or rules to the language. The type-copied class should behave as closely as possible to a class the user has implemented by hand. This should make very easy to understand how the feature works, without the need to grasp many new concepts.
- I have removed the option to modify and remove existing functionality from the class that is being copied. While I believe that this can be useful, it introduces too much complexity in my opinion now. If this is allowed, you basically have to create a system where you are allowed to completely rewrite an existing class starting from another, since you may want to copy or remove or change anything that was previously present. This I believe can both make the proposal unnecessary complicated, and can make the code very hard to follow, as at each new strong-typedef step (since a type could be copied, and the copy copied again and so on) anything could happen. I now believe that an incremental-only strategy (similar to inheritance) can still be both useful and sufficient for most cases. Where it is not, simple implementations by hand of basic functionality, extended then via the type-copy mechanism should result in clear, reusable code which still requires little maintenance.
To be honest, I will vote against this proposal if we can not modify and delete existing functionality. When I copy/paste a class I can change things and remove others.


The word duplicating and wrapping don't match. The proposed approach doesn't wraps the underlying type, except maybe for builtin types.

Right, I'll fix it, thanks.

I believe the proposal needs to add some concrete examples as use cases

This makes sense, I'll worn ok an additional section where I try to show some concrete examples.
The best will be to add them in the motivation section showing how the new feature is used instead of some flat code.


If the base class change, the strong types depending on it could need maintenance also, isn't it?

True, however wrapper methods have to be fixed 100% of the time, while a type-copied class may not need this. It's the same as if one modified a base class in inheritance - you don't have to necessarily update all the code that depends on it right away.
One of the problems of your proposal is that the copied class has access to the private members, which is more subject to changes. I believe that the copied class should only have access to public members.


In addition to these basic techniques, there are other library solution, as e.g. using CRTP that might merit to be mentioned.
See [TBoost.Opaque] for a possible implementation.

I'll add a mention to CRTP. I'll give a look at the boost link, thanks!

Please add http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0109r0.pdf to this list.

Thanks, I must have missed it.

What is wrong for you with p0109r0 approach?
What we could have with your approach that we cannot have with p0109r0?
What we could have with p0109r0 that we cannot have with your approach?

IIUC, p0109r0 opaque types don't introduce any operations by default other than conversions and your proposal introduce all members but no friends

Personally, I don't think there's anything "wrong" with p0109r0. The idea for this proposal came to me before I knew the alternatives, but since C++ still does not have strong typedefs I thought I might as well propose an alternative approach, which could possibly garner more interest.

I believe my approach is simpler to read and more intuitive - especially when creating strong aliases of very complex types, but that can definitely be bias. It can be applied to hierarchies of classes, since it allows type-copying interdependent classes, which I believe is very important. It is very easy to use with templates and specializations, which potentially gives it a lot of power and very many potential uses. It is easily composable, as in copying a copy is very easy and indeed expected in my view for the feature.
You will need to show how all this arguments applies on concrete examples and how p0109r0 could do the same thing.


With p0109r0 there's more flexibility to alter a type. The additional flexibility allows it to take on more tasks, as in type-copying friends, since default ways to convert to the original types exist. It is designed for primitive types first, while I have actually removed them in my last iteration for a number of reasons (see below).
Again, I will vote against this proposal if it doesn't provides a solution for builtin types, as this is IMHO one of the most appealing use cases.

The additional flexibility however comes at a cost, as you mention the return type issue, which my proposal does not have.
Your proposal has already this issue as you copy the whole class, and so you replace every occurrence of the base class with the new class.


I don't introduce friends mostly because a feature to create type-aliases of methods does not currently exist,
I don't know what do you mean by type-aliases of methods. Could you elaborate?

and I don't believe it is wise to add it to the scope of this proposal since it would be another very large change. If such a feature existed, however, adding friends to a type-copied class in my proposal would be trivial, as simply strong typedefs of the needed functions could be created on the fly. The same could also be done for non-member functions if needed, but how that would work I have not thought about yet.
Could you elaborate how type-aliases of methods would help here?


I find that conversions to/from the underlying type must be covered by the proposal. If we change the Base type we don't want to be forced to redefine the conversion operator.
Maybe we need some kind of default conversion implementation
    operator Base() = default;
or
    explicit operator Base() = default;

I like this. I didn't add it to keep the number of features as low as reasonably possible, but this is simple enough. This could also be used recursively, as in a copy of a copy could define a conversion operator to both the first copy and the original in this way.
Could you elaborate?

However, this default way to define a conversion operator would be disabled if the copy has added new attributes with respect to its original class.
Why?


Why do you introduce this restriction? (on template parameter numbers)

No actually, you're right. I should lift it. I thought initially that there wouldn't be any reason to add more template parameters, as they would only be needed to satisfy the original class. But this is definitely wrong. I'll change it, thanks.

In ** above C has not yet defined as a copy

Ill explain how I believe this example should work. Since C has been declared but not defined when parsing D, the compiler will be allowed to assume that C is going to be defined later as a type-copy of A. If that does not happen, then the compiler will give an error, either at the definition of C or of D, explaining that D assumed that C would have been a type-copy while it was not. If C has added new attributes to its declaration though D's definition would need to take the new size of C into account though. Not sure if this can be done or if it should result in an error.
I don't know compiler writers would like to look ahead. I believe that C should be forward declared as a copy.


I believe this interdependent case introduce a lot of complexity. We would need a good use case to introduce it on the standard.

A very simple example would be copying of inherited classes, both base and derived. Without a way to do this that just cannot be done. This can be important if one does not want that the derived type and its type-copy are allowed to be converted to the same base. This is a strong reason, I believe, otherwise strong-typing in that case just lost some power in keeping original and type-copy apart.
I will need a concrete example of when a complete hierarchy must be "duplicated". I'm not saying there are no cases, just I have no one in my head.


I believe that this cannot be optional as in a lot of cases we need to modify/restrict the base type interface.

I have actually removed this in my newest revision. I believe that ground-up building of types is the better way to go (as is normally done in inheritance). Having the power to completely alter the original type, possibly to a point where there was not even much in common between the original and the type-copy, is not worth it. This is a personal opinion, but I believe doing so can prevent some very complex and ugly code smells, at a not incredible cost.
We need to solve concrete problems. The 1st use case I have for strongly types is to reduce the interface provided by the underlying type and to adapt the functions signatures to the new class.


Could you give a concrete example of this kind of problems?

Suppose

struct A {
     int foo(int x) { return x * 2; }
     double bar(double x) { return foo(x) * 4.0; }
};

struct B : using A {
    int foo(int) = delete;
    // bar cannot compile anymore
};

Where is the problem? struct B will not compile. That's all.

struct C : using A {
    double foo(int x) { return x * 2; }
    // ambiguous definition
};

You are not modifying here, but adding ;-). You can say it is ambiguous, but we have this case with inheritance (http://melpon.org/wandbox/permlink/f9mRtE9n5mA9RPpl)

struct D : using A {
    double foo(double x) { return x * 3; }
    // Changes meaning of bar underhandedly with no warning
};

I don't see a problem here as D is a copy of A and then we are defining its meaning.
You said that it should be as simple as if defined the class by hand.

I suppose even more dangerous and complex cases could be devised.
We will need some real examples to see how dangerous they are ;-)


While this is inline with your approach, this merits some explanations as the operators are no members. Shouldn't the operators of UDT classes meritt to be inherited as well?

I've removed primitive type support also for this reason. They are not currently treated as classes by C++, and so I think I shouldn't either. Instead my approach is now constructive. If one needed aliases for primitive types, one could create a single header of wrappers in the form

template <typename T>
class SimpleWrapper {
    public:
        SimpleWrapper(T t) : t_(t) {}

    private:
        T t_;
};

template <typename T>
struct SimpleWrapperWithSum : using SimpleWrapper<T> {
    SimpleWrapperWithSum operator+(const SimpleWrapperWithSum & other) { return t_ + other.t_; }
};

And so on. It would only be needed once, and then users could simply copy the versions with the operators they need to use. It's not incredibly pretty and it does have some limitations, but it works,
I believe it is worth mentioning it in the proposal. My TBoost.Opaque library implements something like that. I will start a new thread to talk about the TBoost.Opaque library approach. If I need a library solution for builtin types, why this library solution will not work for classes, what will be the added value of the language solution?

and in any case even in p0109r0 one would need to remove all unneeded operators, so work would need to be done anyway.
No p0109r0 doesn't define any operation by default (except conversions). The user needs to add whatever is needed, maybe using the default trampoline implementation). Well this is how I understand it.


I believed that there where no implicit conversions on your proposals. How (*this) is converted to an int?

What would be the result type of x below?

Id id(1);
auto x = +id;

I suspect that it would be Id.
Yeah, this was a weird syntax, I thought it could be a simple way to represent conversion to the underlying primitive type.

The type of the operation would be Id, yes. I believe that to be the only intuitive result that makes sense, unless an explicit operator+ that does a conversion has been defined somewhere. If a cast is needed, one needs to cast. I don't see a reason to change that for strong typedefs, otherwise the language would be inconsistent IMO.

Why this restriction is needed or desirable. (w.r.t. final in type-copies of primitive types)

It is not. But I believe that if one needs to do something, it has to be in line with the rest of the language. If a strong typedef of a primitive type is needed, it would still need to follow the rules of primitive types.
It depends. If a strong type is able to define new members it is not anymore a builtin and becomes for me a class.

As primitive types are not inheritable, I believe neither should the strong typedefs. Maybe this is a wrong position, I don't know (since primitive typedefs are not inheritable due to C compatibilities IIUC), but I believe that having consistency is still useful for a proposal. Otherwise one needs to always learn a million exceptions to each rule, and that I don't like, where it can be avoided. In any case, these are my opinions, but if there is strong consensus to change how the proposal works I have no problem in modifying it.
You need to justify your decisions on a rationale section. But as you have removed builtin types as base types this is not needed anymore. You will need to justify why builtin are not supported :( 

Example of where this can be useful welcome.

I must admit I didn't really think about this, I just thought they could be nice to have. Maybe they would be useless. I just considered that sometimes the ingenuity of how people use tools always seem to surprise, so why not. Maybe in order to SFINAE the generation of conversion operators to classes, given that they are copies of the original? Something like

struct Copy : using A {
     template <typename T, /* enable_if_t<is_copy_of_v<T, A>>> */>
     operator T = default;
};
Features must be added to solve concrete problems.

I suggest you to work on the motivation section with real concrete examples.


How other type traits behave for the copied class (see p0109r0)?

All other type traits behave as if the class had been implemented by hand, and is separate from the original. so is_same would return false, for example. It seems that p0109r0 thinks the same way. The only difference is that sizeof may be different, since in my proposal one could add additional attributes to the type-copied class. (There's no examples in the old version, I've added them on GitHub though).
I agree for is_same of course. I was wondering for other traits that could have been specialized for the base class. I suspect the answer is that the user would need to specialize the new type again.


Thanks again for your feedback.
You are welcome,
Vicente

Eugenio Bargiacchi

ungelesen,
31.12.2016, 13:39:1931.12.16
an ISO C++ Standard - Future Proposals
To be honest, I will vote against this proposal if we can not modify and delete existing functionality. When I copy/paste a class I can change things and remove others.

I really understand this feeling. However, consider this: at which point is something a strong typedef of something else, and when is it a new class? Is the mechanism we want to introduce simply a way to declare a new class, using another as a starting point and completely editable, or we want there to be some kind of association (even if it was only on a logical level) between the two things?

A mechanism that freely allows you to edit classes wouldn't be a strong typedef, in my opinion. Consider:

struct A {
     void foo();
};

struct B : using A {
     void foo() = delete;
     void bar();
};

Why would you have copied A in the first place then, rather than create a new class? If instead you could say "copies can only add" then when you find a class declared as a copy you can easily reason about it. Think about a long chains of copies:

struct A { /* .... */ };
struct B : using A { /* .... */ };
struct C : using B { /* .... */ };
struct D : using C { /* .... */ };
struct E : using D { /* .... */ };

All these maybe in different files, in different places. Then what is E? If you are allowed to remove and change things, then you'd have to start at A and manually keep track of which things are deleted, maybe re-added later, maybe modified. It would be really hard to know exactly what E is. While if you're allowed to only increment, it would be much easier to know that - or at least on par with inheritance. If you can find it once in the chain, then it exists. I believe this is an important fact that must be considered when thinking about what features we want in strong typing.


One of the problems of your proposal is that the copied class has access to the private members, which is more subject to changes. I believe that the copied class should only have access to public members.

If one creates a new class, and private members cannot be accessed, there will not be any way to touch them to do anything else aside from the original interface. This is also compounded by the fact that all old methods accepting the original class won't work on the new class (creating an operator Base() should be an exception for certain use-cases, it should not be necessary to do so). How are you going to print a copy? How can you convert in a custom way to the original class? How are you going to implement additional and useful methods without access to privates?


Again, I will vote against this proposal if it doesn't provides a solution for builtin types, as this is IMHO one of the most appealing use cases.

A solution exists, and is the one that already exists when one wants to limit operations done on existing types: build a wrapper. The difference is that before a completely new wrapper had to be built from scratch for each new needed primitive alias, depending on the required operations, with this proposal they become easily composable, so once done it never has to be done again, and adding new types becomes a one-liner for the user.

Consider this against what p0109r0 does:

template <class T = double>
using energy = protected double {
    energy operator+ (energy , energy) = default;
    energy& operator*=(energy&, T ) = default;
    energy operator*(energy , energy) = delete;
    energy operator*(energy , T ) = default;
    energy operator*(T , energy) = default;
};

You see that in order to have a custom type, still many things must be written. So what's the difference between creating copyable wrappers (using normal C++ rules), and creating a number of such interfaces (having to learn and teach how protected/public/private work in a new context)? I don't see what the difference would be. Why do you feel that wrappers on primitive types are not enough?


Your proposal has already this issue as you copy the whole class, and so you replace every occurrence of the base class with the new class.

I'm not sure what you mean. In my proposal, the copy is a new class, and so it is always obvious what an assignment operator is going to result in. In p0109r0 this is more complex because depending on the access specifier different default conversion operators are defined. Not sure what you mean when you mention the replacement that happens, and what the issue is.


I don't know what do you mean by type-aliases of methods. Could you elaborate?

Yes. So consider this:

struct A {
    friend void foo(A&);
};

struct B : using A {};

To me the only way to allow B to also have the friend declaration is if we could do the following transformation:

void foo(A&) ----> void foo(B&)

In some sense creating a new foo overload using the type-copy. However, I believe this would be a pretty big change. If such a thing was allowed, then why stop at copies? Why not being able to define

void foo(A&) ----> void foo(C&)

or even

void foo(A&) ----> template<typename T> void foo(T&);

As this is outside of the scope of my proposal, I did not include this. And so, I cannot add to my proposal the conversion of friend and non-member function when creating a copy-type. If this could be included, there would be no problem for that.

Could you elaborate? (on recursive conversion operator)

For example, consider

struct A {};
struct B : using A {
    operator A() = default;
};
struct C : using B {
    operator B() = default;
    operator A() = default;
};

Just an idea.


However, this default way to define a conversion operator would be disabled if the copy has added new attributes with respect to its original class.
Why?

I think that at that point the two classes diverged enough that the compiler cannot assume they can be converted freely? If I have:

struct A { int x; };
struct B : using A { int y; };

Is it really wise to allow the compiler to be able to default convert B to A? If they were the same, ok, but maybe at that point the user should be forced to provide a conversion operator, no?


I don't know compiler writers would like to look ahead. I believe that C should be forward declared as a copy.

Maybe. I thought that inherited classes are also declared without mentioning that they inherit, so I though that the same should hold for copies.

I will need a concrete example of when a complete hierarchy must be "duplicated". I'm not saying there are no cases, just I have no one in my head.

I'll try to add these on the concrete examples.


You are not modifying here, but adding ;-). You can say it is ambiguous, but we have this case with inheritance (http://melpon.org/wandbox/permlink/f9mRtE9n5mA9RPpl)

Not quite, the resulting copied class would be equivalent to

struct C {
     int foo(int x) { return x * 2; }
     double foo(int x) { return x * 2; }
     double bar(double x) { return foo(x) * 4.0; }
};

which is illegal, since you cannot overload on return type. Keep in mind that there is no "layer" between the original class and what gets added to the copy.


I don't see a problem here as D is a copy of A and then we are defining its meaning.
You said that it should be as simple as if defined the class by hand.

That's true, and that's mostly what I don't like about giving the ability to modify copied existing methods. It is definitely non-obvious that this is happening. If you make a mistake, it will take a long time to figure out that you involuntarily modified the behavior of the original class. That's bad in my book.


I believe it is worth mentioning it in the proposal. My TBoost.Opaque library implements something like that. I will start a new thread to talk about the TBoost.Opaque library approach. If I need a library solution for builtin types, why this library solution will not work for classes, what will be the added value of the language solution?

I believe that wrapper types are just as good as primitive types. This is also how it's always been in C++. The main problem was simply that doing this over and over and over was incredibly unwieldy. I don't believe that the reason why the older, in-C++ approaches didn't work is that they were creating wrappers rather than "primitive type aliases". This proposal eliminates the need for repetition as once done, it is done forever. A library like boost can pre-produce often used primitive types wrappers once in a single header, and be done forever.


No p0109r0 doesn't define any operation by default (except conversions). The user needs to add whatever is needed, maybe using the default trampoline implementation). Well this is how I understand it.

From the proposal: "Since all member functions of the underlying type are known to the compiler, we can take advantage of this and therefore propose that the compiler, by default, generate default versions of these trampolines."

Features must be added to solve concrete problems.

I suggest you to work on the motivation section with real concrete examples.

Yes, I'll work on those, promise! ;)

--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/gkJUVnL-Fmg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.

Vicente J. Botet Escriba

ungelesen,
01.01.2017, 10:57:0301.01.17
an std-pr...@isocpp.org
Le 31/12/2016 à 19:39, Eugenio Bargiacchi a écrit :

Eugenio, please, preserve some additional context in your responses.

From you responses, I believe that I have not understood your proposal. See below (***)
To be honest, I will vote against this proposal if we can not modify and delete existing functionality. When I copy/paste a class I can change things and remove others.

I really understand this feeling. However, consider this: at which point is something a strong typedef of something else, and when is it a new class? Is the mechanism we want to introduce simply a way to declare a new class, using another as a starting point and completely editable, or we want there to be some kind of association (even if it was only on a logical level) between the two things?
I see that you want to add a new mechanism to define a new class using another class as starting point, but I would not name this a strong type proposal, even if it can be used to build some strong types. Whether this is useful by itself needs more motivation and concrete real examples.


A mechanism that freely allows you to edit classes wouldn't be a strong typedef, in my opinion. Consider:

struct A {
     void foo();
};

struct B : using A {
     void foo() = delete;
     void bar();
};

Why would you have copied A in the first place then, rather than create a new class?
Add just N bazN() functions to A and you will see why it is interesting to modify B because B doesn't want to provide the foo function, but all the bazN functions.

If instead you could say "copies can only add" then when you find a class declared as a copy you can easily reason about it. Think about a long chains of copies:

struct A { /* .... */ };
struct B : using A { /* .... */ };
struct C : using B { /* .... */ };
struct D : using C { /* .... */ };
struct E : using D { /* .... */ };

All these maybe in different files, in different places. Then what is E? If you are allowed to remove and change things, then you'd have to start at A and manually keep track of which things are deleted, maybe re-added later, maybe modified. It would be really hard to know exactly what E is. While if you're allowed to only increment, it would be much easier to know that - or at least on par with inheritance. If you can find it once in the chain, then it exists. I believe this is an important fact that must be considered when thinking about what features we want in strong typing.
Note that inheritance allows you to make a member not accessible.

struct A {
     int foo(int x) { return x * 2; }
     double bar(double x) { return foo(x) * 4.0; }
};

struct B : A {
     private:
        double bar(double x);
};


One of the problems of your proposal is that the copied class has access to the private members, which is more subject to changes. I believe that the copied class should only have access to public members.

If one creates a new class, and private members cannot be accessed, there will not be any way to touch them to do anything else aside from the original interface.
Right.

This is also compounded by the fact that all old methods accepting the original class won't work on the new class (creating an operator Base() should be an exception for certain use-cases, it should not be necessary to do so).
The Base conversion can be protected or private. The trampoline would take care of this conversions.

How are you going to print a copy?
Hmm, using the public interface or don't.

How can you convert in a custom way to the original class?
I don't see the use case. Maybe you have one.

How are you going to implement additional and useful methods without access to privates?
If we need to access to private I believe it is worth creating a new class.

Again, I will vote against this proposal if it doesn't provides a solution for builtin types, as this is IMHO one of the most appealing use cases.

A solution exists, and is the one that already exists when one wants to limit operations done on existing types: build a wrapper. The difference is that before a completely new wrapper had to be built from scratch for each new needed primitive alias, depending on the required operations, with this proposal they become easily composable, so once done it never has to be done again, and adding new types becomes a one-liner for the user.
What I mean is that these wrappers should be part of the proposal and show how the your copy class reach to make concrete and real opaque types as e.g the energy example in P0109.


Consider this against what p0109r0 does:

template <class T = double>
using energy = protected double {
    energy operator+ (energy , energy) = default;
    energy& operator*=(energy&, T ) = default;
    energy operator*(energy , energy) = delete;
    energy operator*(energy , T ) = default;
    energy operator*(T , energy) = default;
};

You see that in order to have a custom type, still many things must be written. So what's the difference between creating copyable wrappers (using normal C++ rules), and creating a number of such interfaces (having to learn and teach how protected/public/private work in a new context)? I don't see what the difference would be. Why do you feel that wrappers on primitive types are not enough?
Yes, the new opaque type must define all the available functions. Note that p0109 provides conversions and the possibility to request the compiler to generate the default trampolines. This is much sorter than defining them using the current C++ language. Implicit conversions (public) allow to use the new opaque type where the underlying type was expected.

I would prefer the opaque type proposal to go even further (or an independent proposal) and be able to introduce groups of trampolines as I did on my Opaque library with the combined mixins. However I have no concrete proposal at the language level.


Your proposal has already this issue as you copy the whole class, and so you replace every occurrence of the base class with the new class.

I'm not sure what you mean. In my proposal, the copy is a new class, and so it is always obvious what an assignment operator is going to result in. In p0109r0 this is more complex because depending on the access specifier different default conversion operators are defined. Not sure what you mean when you mention the replacement that happens, and what the issue is.


struct A {
    A append(A const&);
};

struct B : using A {};

What will be the type of x

B b1, b2;
auto x = b1.append(b2);

IIUC your proposal, the append function will be copied replacing any occurrence of A by B, so it is as if B was declared

struct B {
    B append(B const&);
};

Am I missing something? (***)

Or would B equivalent to

struct B {
    A append(B const&);
};

or to

struct B {
    A append(A const&);
};

I'm wondering now if the copied class could add at all new non-static data members. Otherwise I don't see how the duplication of such function can be done. I suspect that p0109 doesn't allow to add new data.
Then, if the user wants to add more data, it should first duplicate the class and then inherit from the duplicated class to add more data.


I don't know what do you mean by type-aliases of methods. Could you elaborate?

Yes. So consider this:

struct A {
    friend void foo(A&);
};

struct B : using A {};

To me the only way to allow B to also have the friend declaration is if we could do the following transformation:

void foo(A&) ----> void foo(B&)
Right.


In some sense creating a new foo overload using the type-copy. However, I believe this would be a pretty big change.
I believed this was already part of your proposal. (***)

If such a thing was allowed, then why stop at copies? Why not being able to define

void foo(A&) ----> void foo(C&)

or even

void foo(A&) ----> template<typename T> void foo(T&);

As this is outside of the scope of my proposal, I did not include this. And so, I cannot add to my proposal the conversion of friend and non-member function when creating a copy-type. If this could be included, there would be no problem for that.
Sorry, I don't know yet what are type-aliases of methods.

Could you elaborate? (on recursive conversion operator)

For example, consider

struct A {};
struct B : using A {
    operator A() = default;
};
struct C : using B {
    operator B() = default;
    operator A() = default;
};

Just an idea.
I'm lost.


However, this default way to define a conversion operator would be disabled if the copy has added new attributes with respect to its original class.
Why?

I think that at that point the two classes diverged enough that the compiler cannot assume they can be converted freely? If I have:

struct A { int x; };
struct B : using A { int y; };

Is it really wise to allow the compiler to be able to default convert B to A?
I would say, yes. The question p0109 raise is whether the user wants it.

If they were the same, ok, but maybe at that point the user should be forced to provide a conversion operator, no?
If the user decides to provide the default implementation I don't see where the problem is.

operator Base() = default;

The compiler know how to do the conversion.


I don't know compiler writers would like to look ahead. I believe that C should be forward declared as a copy.

Maybe. I thought that inherited classes are also declared without mentioning that they inherit, so I though that the same should hold for copies.
Maybe, but the C++ standard doesn't have constrains on whether the class is derived from another class.


I will need a concrete example of when a complete hierarchy must be "duplicated". I'm not saying there are no cases, just I have no one in my head.

I'll try to add these on the concrete examples.

You are not modifying here, but adding ;-). You can say it is ambiguous, but we have this case with inheritance (http://melpon.org/wandbox/permlink/f9mRtE9n5mA9RPpl)

Not quite, the resulting copied class would be equivalent to

struct C {
     int foo(int x) { return x * 2; }
     double foo(int x) { return x * 2; }
     double bar(double x) { return foo(x) * 4.0; }
};

which is illegal, since you cannot overload on return type. Keep in mind that there is no "layer" between the original class and what gets added to the copy.
You are right. I missed that all the function are copied. In this case, either a function is not copied when there is an added function with the same overloaded signature or the program is ill formed.


I don't see a problem here as D is a copy of A and then we are defining its meaning.
You said that it should be as simple as if defined the class by hand.

That's true, and that's mostly what I don't like about giving the ability to modify copied existing methods. It is definitely non-obvious that this is happening. If you make a mistake, it will take a long time to figure out that you involuntarily modified the behavior of the original class. That's bad in my book.
Maybe you need something like override to state clearly that you want to redefine the inherited behavior.


I believe it is worth mentioning it in the proposal. My TBoost.Opaque library implements something like that. I will start a new thread to talk about the TBoost.Opaque library approach. If I need a library solution for builtin types, why this library solution will not work for classes, what will be the added value of the language solution?

I believe that wrapper types are just as good as primitive types. This is also how it's always been in C++. The main problem was simply that doing this over and over and over was incredibly unwieldy. I don't believe that the reason why the older, in-C++ approaches didn't work is that they were creating wrappers rather than "primitive type aliases". This proposal eliminates the need for repetition as once done, it is done forever. A library like boost can pre-produce often used primitive types wrappers once in a single header, and be done forever.

No p0109r0 doesn't define any operation by default (except conversions). The user needs to add whatever is needed, maybe using the default trampoline implementation). Well this is how I understand it.

From the proposal: "Since all member functions of the underlying type are known to the compiler, we can take advantage of this and therefore propose that the compiler, by default, generate default versions of these trampolines."
I interpret this as the compiler could generate them by default, once the user has requested it using the = default syntax. I agree the proposal doesn't contain examples where this is clear enough.



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

Eugenio Bargiacchi

ungelesen,
01.01.2017, 12:26:1901.01.17
an ISO C++ Standard - Future Proposals
Dear Vicente,


Eugenio, please, preserve some additional context in your responses.

Sorry, I'll try to be more careful.

I really understand this feeling. However, consider this: at which point is something a strong typedef of something else, and when is it a new class? Is the mechanism we want to introduce simply a way to declare a new class, using another as a starting point and completely editable, or we want there to be some kind of association (even if it was only on a logical level) between the two things
I see that you want to add a new mechanism to define a new class using another class as starting point, but I would not name this a strong type proposal, even if it can be used to build some strong types. Whether this is useful by itself needs more motivation and concrete real examples.
I don't understand. Even what you want from a strong typing proposal is still to define a new class from an old class. The only difference I see between what we are talking about is that I don't think editing the old class is useful enough compared to the downsides, and you don't.


Why would you have copied A in the first place then, rather than create a new class?
Add just N bazN() functions to A and you will see why it is interesting to modify B because B doesn't want to provide the foo function, but all the bazN functions.

I do see that. The problem is that once a feature is out you don't get to tell people how to use it. And people WILL misuse having the power to alter everything every time. I don't believe it is possible to assume that this won't happen. Base on my personal experience, this will happen a lot, and it will be bad.

Note that inheritance allows you to make a member not accessible.
struct A {
     int foo(int x) { return x * 2; }
     double bar(double x) { return foo(x) * 4.0; }
};

struct B : A {
     private:
        double bar(double x);
};

I assume that you wanted to use public inheritance. With private inheritance being an equivalent of encapsulation, the assertion would not be interesting, since wrapping of course removes access to the encapsulated data. With public inheritance, you did not make that member not accessible. It is still so, you have just hidden it.

B b;
b.A::bar(5);

There is a difference. In addition, I don't believe that two wrongs make a right. Making a feature that can be potentially abused in the worst ways is not wise, I think.


One of the problems of your proposal is that the copied class has access to the private members, which is more subject to changes. I believe that the copied class should only have access to public members.

If one creates a new class, and private members cannot be accessed, there will not be any way to touch them to do anything else aside from the original interface.
Right.
This is also compounded by the fact that all old methods accepting the original class won't work on the new class (creating an operator Base() should be an exception for certain use-cases, it should not be necessary to do so).
The Base conversion can be protected or private. The trampoline would take care of this conversions.
How are you going to print a copy?
Hmm, using the public interface or don't.
How can you convert in a custom way to the original class?
I don't see the use case. Maybe you have one.
How are you going to implement additional and useful methods without access to privates?
If we need to access to private I believe it is worth creating a new class.

If I understand correctly, you want to be able to create a copy of another class, with the ability to alter and access its public interface only. What happens when you delete a public method used by a private method? What happens when you delete a public attribute used in private code? What happens to protected code - if not accessible you basically make inheritance useless on it? If in all these cases your answer is "the program will fail to compile, just copy the class by hand" then I think the feature would be severely limited in what it could actually do.

I feel that what you are proposing is basically a fast way to create PIMPL wrappers. If I understand correctly, you want to hide the original completely, and just be offered an easy way to bring back some of its interface (via trampolines) to its "wrapper", which would be the strong typedef. Possibly adding more public methods only using the public interface, which could be done just the same with non-member methods. The end result is a class that cannot be extended in any meaningful way using any of the normal C++ facilities, since every juicy bit is hidden. Applying inheritance to it would require manual exposure of all methods, with the limitations we have already discussed. Applying strong-typing again to a strong-typed class would be useless as access to internals would be the same, aside from possibly adding more data (if needed) in the public attributes, since only those are editable. This would also prevent encapsulation of new data. If not, it would just mean that the feature wouldn't be used in a composable way.

My own idea is to be able to extend an already existing class into a new class. I may not have 100% use cases for all things I'm proposing, but that is simply because I can't write in advance all possible programs with the feature. I just feel (maybe feeling is not enough, but opinions are also made of these unfortunately) that if a new feature has to be introduced it should at least try to play ball with the rest of the language. The more it does, the more it can be used in any weird case that may come up. One of the most beautiful things about C++ is that there's pretty much always a way to do something, exactly because the language is flexible and its parts can be combined in many possible ways. A feature that creates a non-extendable object is like a dead-end.


What I mean is that these wrappers should be part of the proposal and show how the your copy class reach to make concrete and real opaque types as e.g the energy example in P0109.

But wrappers can be made in a separate library. Why should they be included in the proposal? As long as the proposal allows them, a library only needs to be made once (and if not needed by someone, not used).
You see that in order to have a custom type, still many things must be written. So what's the difference between creating copyable wrappers (using normal C++ rules), and creating a number of such interfaces (having to learn and teach how protected/public/private work in a new context)? I don't see what the difference would be. Why do you feel that wrappers on primitive types are not enough?
Yes, the new opaque type must define all the available functions. Note that p0109 provides conversions and the possibility to request the compiler to generate the default trampolines. This is much sorter than defining them using the current C++ language. Implicit conversions (public) allow to use the new opaque type where the underlying type was expected.

I would prefer the opaque type proposal to go even further (or an independent proposal) and be able to introduce groups of trampolines as I did on my Opaque library with the combined mixins. However I have no concrete proposal at the language level.

But I have made you an example with wrappers, which you also agreed is similar to what your library currently does. So what is wrong with that approach?

struct A {
    A append(A const&);
};

struct B : using A {};

What will be the type of x

B b1, b2;
auto x = b1.append(b2);

IIUC your proposal, the append function will be copied replacing any occurrence of A by B, so it is as if B was declared

struct B {
    B append(B const&);
};

Am I missing something? (***)

Or would B equivalent to

struct B {
    A append(B const&);
};

or to

struct B {
    A append(A const&);
};

I'm wondering now if the copied class could add at all new non-static data members. Otherwise I don't see how the duplication of such function can be done. I suspect that p0109 doesn't allow to add new data.
Then, if the user wants to add more data, it should first duplicate the class and then inherit from the duplicated class to add more data.

No, you understood correctly, the end result would be

struct B {
    B append(B const&);
};

Forcing the user to inherit to add more data adds inheritance, which means that types are now implicitly convertible (to at least a common base). I don't think this is desirable. I think strong typing should work on its own.


In some sense creating a new foo overload using the type-copy. However, I believe this would be a pretty big change.
I believed this was already part of your proposal. (***)

Mhh, now that you put it this way. I thought about only doing this for classes. While the mechanism for free functions would be the same (that's what I mean by strong-typedef of a function, see below), the scope would be wider. One only works for classes, the other on all free functions. I don't know, maybe dividing them makes no sense. I'd love to hear what you think about this.

As this is outside of the scope of my proposal, I did not include this. And so, I cannot add to my proposal the conversion of friend and non-member function when creating a copy-type. If this could be included, there would be no problem for that.
Sorry, I don't know yet what are type-aliases of methods.

I'll try to explain myself better. A strong typedef of a function would be taking that function and replacing one or more types within it to others. You could say that when you do:

template <typename T>
void foo(T);

then foo<int> and foo<double> are "strong typedefs" of each other, in some sense. If one were allowed to do this to existing code, one could do something like

void foo(int);

void foo(double) : using foo(int);

to use an analogous syntax to the one in my proposal. Not sure if this is clearer, let me know.

For example, consider

struct A {};
struct B : using A {
    operator A() = default;
};
struct C : using B {
    operator B() = default;
    operator A() = default;
};

Just an idea.
I'm lost.

What I mean is, if B is a strong typedef of A, then we can default its conversion operator to A, right? But then, if C is a type-copy of B, then we can default its conversion operator to both B (since it's its type-copy), and also to A (since C is a type-copy of a type-copy).


Is it really wise to allow the compiler to be able to default convert B to A?
I would say, yes. The question p0109 raise is whether the user wants it.

I'm not sure, however to me there's no difference either way.


Maybe. I thought that inherited classes are also declared without mentioning that they inherit, so I though that the same should hold for copies.
Maybe, but the C++ standard doesn't have constrains on whether the class is derived from another class.

True. I'll try to think about this a bit more. Maybe we could simply lift the restriction and let the user do what he/she wants? (so also use classes that are not necessarily copies of the originals, as long as their interface is compatible to what it needs to be)
That's true, and that's mostly what I don't like about giving the ability to modify copied existing methods. It is definitely non-obvious that this is happening. If you make a mistake, it will take a long time to figure out that you involuntarily modified the behavior of the original class. That's bad in my book.
Maybe you need something like override to state clearly that you want to redefine the inherited behavior.

Unfortunately there would be nothing to override, as you are adding a new overload completely separate to the old one. Both would still be usable and work, nothing is being redefined. Simply the old function would get linked against the new implementation. There's nothing that the compiler could do to prevent this.

Best,
Eugenio

To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.

To post to this group, send email to std-pr...@isocpp.org.

--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/gkJUVnL-Fmg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.

Vicente J. Botet Escriba

ungelesen,
01.01.2017, 18:30:2401.01.17
an std-pr...@isocpp.org
Le 01/01/2017 à 18:26, Eugenio Bargiacchi a écrit :

Eugenio, I believe the use cases we have both in mind are quite different, and so we don't reach to understand one to each other.

I have a question. Does you copy class proposal needs to have the definition of the copied class available?
If yes, I don't think this could be acceptable.
If the declaration is enough, the copied functions would need at least an internal conversion to/from the underlying type, isn't it?


Note that inheritance allows you to make a member not accessible.
struct A {
     int foo(int x) { return x * 2; }
     double bar(double x) { return foo(x) * 4.0; }
};

struct B : A {
     private:
        double bar(double x);
};

I assume that you wanted to use public inheritance. With private inheritance being an equivalent of encapsulation, the assertion would not be interesting, since wrapping of course removes access to the encapsulated data. With public inheritance, you did not make that member not accessible. It is still so, you have just hidden it.

B b;
b.A::bar(5);

Agreed. And the language let me do that even if you find it could be bad practice. I don't see why your copy approach wouldn't let me do that.

There is a difference. In addition, I don't believe that two wrongs make a right. Making a feature that can be potentially abused in the worst ways is not wise, I think.

One of the problems of your proposal is that the copied class has access to the private members, which is more subject to changes. I believe that the copied class should only have access to public members.

If one creates a new class, and private members cannot be accessed, there will not be any way to touch them to do anything else aside from the original interface.
Right.
This is also compounded by the fact that all old methods accepting the original class won't work on the new class (creating an operator Base() should be an exception for certain use-cases, it should not be necessary to do so).
The Base conversion can be protected or private. The trampoline would take care of this conversions.
How are you going to print a copy?
Hmm, using the public interface or don't.
How can you convert in a custom way to the original class?
I don't see the use case. Maybe you have one.
How are you going to implement additional and useful methods without access to privates?
If we need to access to private I believe it is worth creating a new class.

If I understand correctly, you want to be able to create a copy of another class, with the ability to alter and access its public interface only. What happens when you delete a public method used by a private method?
Compile error ?

What happens when you delete a public attribute used in private code?
We can not remove data members, otherwise we can not share the same representation.

What happens to protected code - if not accessible you basically make inheritance useless on it?
Good question. With the wrapping approach, you couldn't inherit from it. With you approach, the data will be copied. The question is if the strong-type has access to this protected data.
I have never needed to create a duplicate of a class that is part of inheritance hierarchy. I've always wrapped final classes. Some examples will help me to clarify the need.
I believe we could have duplication having access only to the public part and let the derived classes use the protected part. Nevertheless I could live providing access to the protected part.

If in all these cases your answer is "the program will fail to compile, just copy the class by hand" then I think the feature would be severely limited in what it could actually do.
You are probably right, and the copy should copy every thing and give access to everything internally. I need some concrete examples to understand what this possibility will solve.


I feel that what you are proposing is basically a fast way to create PIMPL wrappers.
Not at all.

If I understand correctly, you want to hide the original completely, and just be offered an easy way to bring back some of its interface (via trampolines) to its "wrapper", which would be the strong typedef.
I wouldn't say hiding, just don't looking inside the underlying type. I want to provide access to it (when needed) but only as a whole.

Possibly adding more public methods only using the public interface, which could be done just the same with non-member methods. The end result is a class that cannot be extended in any meaningful way using any of the normal C++ facilities, since every juicy bit is hidden.
Why it is unusable? The class provide access to everything you want.

Applying inheritance to it would require manual exposure of all methods, with the limitations we have already discussed.
I don't follow you here.

Applying strong-typing again to a strong-typed class would be useless as access to internals would be the same,
You can strong-type again. I don't see the trouble. E.g; you can have a energy strong-type and have kinetic_energy, potential_energy and  heat_energy strong-type of energy.
Typing and representation are two different things. Strong types are just that. Share a representation and have different types.

aside from possibly adding more data (if needed) in the public attributes, since only those are editable. This would also prevent encapsulation of new data. If not, it would just mean that the feature wouldn't be used in a composable way.
I believe now that strong-type shouldn't add any new non-static data member.


My own idea is to be able to extend an already existing class into a new class. I may not have 100% use cases for all things I'm proposing, but that is simply because I can't write in advance all possible programs with the feature. I just feel (maybe feeling is not enough, but opinions are also made of these unfortunately) that if a new feature has to be introduced it should at least try to play ball with the rest of the language. The more it does, the more it can be used in any weird case that may come up. One of the most beautiful things about C++ is that there's pretty much always a way to do something, exactly because the language is flexible and its parts can be combined in many possible ways. A feature that creates a non-extendable object is like a dead-end.
I must agree with your last sentence, but I don't think the strong-types I'm suggesting are non-extendable?


What I mean is that these wrappers should be part of the proposal and show how the your copy class reach to make concrete and real opaque types as e.g the energy example in P0109.

But wrappers can be made in a separate library. Why should they be included in the proposal? As long as the proposal allows them, a library only needs to be made once (and if not needed by someone, not used).
We need some probe of concept applied to concrete examples we want to solve. I believe a strong type feature must solve the builtin type case. If your proposal is not a strong type proposal, then forget my comments.

You see that in order to have a custom type, still many things must be written. So what's the difference between creating copyable wrappers (using normal C++ rules), and creating a number of such interfaces (having to learn and teach how protected/public/private work in a new context)? I don't see what the difference would be. Why do you feel that wrappers on primitive types are not enough?
Yes, the new opaque type must define all the available functions. Note that p0109 provides conversions and the possibility to request the compiler to generate the default trampolines. This is much sorter than defining them using the current C++ language. Implicit conversions (public) allow to use the new opaque type where the underlying type was expected.

I would prefer the opaque type proposal to go even further (or an independent proposal) and be able to introduce groups of trampolines as I did on my Opaque library with the combined mixins. However I have no concrete proposal at the language level.

But I have made you an example with wrappers, which you also agreed is similar to what your library currently does. So what is wrong with that approach?
I believe we are not understanding one each other. My two last paragraphs don't concern your proposal. It concerns p0109.


struct A {
    A append(A const&);
};

struct B : using A {};

What will be the type of x

B b1, b2;
auto x = b1.append(b2);

IIUC your proposal, the append function will be copied replacing any occurrence of A by B, so it is as if B was declared

struct B {
    B append(B const&);
};

Am I missing something? (***)

Or would B equivalent to

struct B {
    A append(B const&);
};

or to

struct B {
    A append(A const&);
};

I'm wondering now if the copied class could add at all new non-static data members. Otherwise I don't see how the duplication of such function can be done. I suspect that p0109 doesn't allow to add new data.
Then, if the user wants to add more data, it should first duplicate the class and then inherit from the duplicated class to add more data.

No, you understood correctly, the end result would be

struct B {
    B append(B const&);
};

Great.

Forcing the user to inherit to add more data adds inheritance, which means that types are now implicitly convertible (to at least a common base). I don't think this is desirable. I think strong typing should work on its own.
How then you will be able to construct a B from a A if B adds more non-static data members?


In some sense creating a new foo overload using the type-copy. However, I believe this would be a pretty big change.
I believed this was already part of your proposal. (***)

Mhh, now that you put it this way. I thought about only doing this for classes.
Do you mean for member functions?

While the mechanism for free functions would be the same (that's what I mean by strong-typedef of a function, see below), the scope would be wider. One only works for classes, the other on all free functions. I don't know, maybe dividing them makes no sense. I'd love to hear what you think about this.
As you want to inherit all the member functions from the underlying class, it has a sens to concentrate on member functions. I don't understand why friend functions could not follow the same schema.

As this is outside of the scope of my proposal, I did not include this. And so, I cannot add to my proposal the conversion of friend and non-member function when creating a copy-type. If this could be included, there would be no problem for that.
Sorry, I don't know yet what are type-aliases of methods.

I'll try to explain myself better. A strong typedef of a function would be taking that function and replacing one or more types within it to others. You could say that when you do:

template <typename T>
void foo(T);

then foo<int> and foo<double> are "strong typedefs" of each other, in some sense. If one were allowed to do this to existing code, one could do something like

void foo(int);

void foo(double) : using foo(int);

to use an analogous syntax to the one in my proposal. Not sure if this is clearer, let me know.
I believe I understand you now but I don't see how this could help.

For example, consider

struct A {};
struct B : using A {
    operator A() = default;
};
struct C : using B {
    operator B() = default;
    operator A() = default;
};

Just an idea.
I'm lost.

What I mean is, if B is a strong typedef of A, then we can default its conversion operator to A, right?
Right.

But then, if C is a type-copy of B, then we can default its conversion operator to both B (since it's its type-copy),
Right

and also to A (since C is a type-copy of a type-copy).
The first Opaque proposal contained a implicit subsumption relation that allowed that. I'm all for, but I believe the committee doesn't like transitivity in conversions.


Is it really wise to allow the compiler to be able to default convert B to A?
I would say, yes. The question p0109 raise is whether the user wants it.

I'm not sure, however to me there's no difference either way.
The compiler is always able to do the conversion and the user defines when the compiler will do it.


Maybe. I thought that inherited classes are also declared without mentioning that they inherit, so I though that the same should hold for copies.
Maybe, but the C++ standard doesn't have constrains on whether the class is derived from another class.

True. I'll try to think about this a bit more. Maybe we could simply lift the restriction and let the user do what he/she wants? (so also use classes that are not necessarily copies of the originals, as long as their interface is compatible to what it needs to be)
Maybe.

That's true, and that's mostly what I don't like about giving the ability to modify copied existing methods. It is definitely non-obvious that this is happening. If you make a mistake, it will take a long time to figure out that you involuntarily modified the behavior of the original class. That's bad in my book.
Maybe you need something like override to state clearly that you want to redefine the inherited behavior.

Unfortunately there would be nothing to override, as you are adding a new overload completely separate to the old one. Both would still be usable and work, nothing is being redefined. Simply the old function would get linked against the new implementation. There's nothing that the compiler could do to prevent this.
Well, this is your proposal, and if you don't want to be able to modify the duplicate class, you are then right. But if you where able to modify the duplicated class, overriden could have a sense, to mean replace it.

Best,
Eugenio
Vicente

Eugenio Bargiacchi

ungelesen,
02.01.2017, 07:31:3502.01.17
an ISO C++ Standard - Future Proposals
Dear Vicente,


Eugenio, I believe the use cases we have both in mind are quite different, and so we don't reach to understand one to each other.

It seems so. I am available to try other forms of communications if you want (chat, voice call) if you want. I'm sorry I'm not being as clear as I want.


I have a question. Does you copy class proposal needs to have the definition of the copied class available?
If yes, I don't think this could be acceptable.
If the declaration is enough, the copied functions would need at least an internal conversion to/from the underlying type, isn't it?

My proposal does not need the definition. Yes, I suppose an internal conversion would be needed. The internal conversion would probably work similarly to how inheritance works, even if adding non-static members was allowed. As in, the compiler knows that the first piece of the class will be the same as the original, and every new member is added at the end.

B b;
b.A::bar(5);

Agreed. And the language let me do that even if you find it could be bad practice. I don't see why your copy approach wouldn't let me do that.

In my approach the old function is impossible to hide, so I think that the two cases are different.

If in all these cases your answer is "the program will fail to compile, just copy the class by hand" then I think the feature would be severely limited in what it could actually do.
You are probably right, and the copy should copy every thing and give access to everything internally. I need some concrete examples to understand what this possibility will solve.

Well, for example, a library that wanted to create strong typedefs of primitive types via wrappers and only expose certain operations, without getters/setters ;) I've added some code later to show you how a possible implementation using this proposal.

If I understand correctly, you want to hide the original completely, and just be offered an easy way to bring back some of its interface (via trampolines) to its "wrapper", which would be the strong typedef.
I wouldn't say hiding, just don't looking inside the underlying type. I want to provide access to it (when needed) but only as a whole.
Possibly adding more public methods only using the public interface, which could be done just the same with non-member methods. The end result is a class that cannot be extended in any meaningful way using any of the normal C++ facilities, since every juicy bit is hidden.
Why it is unusable? The class provide access to everything you want.

Classes don't always come with getters and setters for every private field. Especially if the interface they provide is a high level one. It happens often in low-level code that you want to interact with the underlying bits in very specific ways, but you don't necessarily want to give that kind of access to users. So creating a strong typedef of such a class while adding a new mode of operation would be impossible, as the copy won't have access to the underlying implementation.

Another could be when you have to types that you know contain the same (private) data, but should expose different public interfaces. Then you could first create the class containing the data, and copy it two times, each time exposing a different interface (which would need access to the private data). Once again, I unfortunately don't have a practical use case at the moment. I just think it could be useful.

Still the code below is a good use-case I think, since it would be needed to provide strong typedefs of primitive types.
Applying strong-typing again to a strong-typed class would be useless as access to internals would be the same,
You can strong-type again. I don't see the trouble. E.g; you can have a energy strong-type and have kinetic_energy, potential_energy and  heat_energy strong-type of energy.
Typing and representation are two different things. Strong types are just that. Share a representation and have different types.

Yes, of course. What I mean is, you have created a class which can only create exact copies of it. If you need additional changes, it will probably be easier to strong-type the original again and create new trampolines from that. This is what I mean by non-extendable.

aside from possibly adding more data (if needed) in the public attributes, since only those are editable. This would also prevent encapsulation of new data. If not, it would just mean that the feature wouldn't be used in a composable way.
I believe now that strong-type shouldn't add any new non-static data member

I start to see what you mean. But still, if one could add them, the copy would still "contain" an equivalent of the original class at its beginning. It wouldn't be guaranteed reinterpret_cast-able, but the fields would be there anyway. The original representation is there - so to me it could still be considered a strong-typedef (stretching a bit the definition maybe).

Is there any other reason why you don't like the idea?
A feature that creates a non-extendable object is like a dead-end.
I must agree with your last sentence, but I don't think the strong-types I'm suggesting are non-extendable?

Well, if only the public interface is available, than adding new methods to it is pretty much equivalent with creating non-member functions (since those work with public interface). Then you might as well remove the ability to add new member functions, since non-members will do just fine and there's no need to have them as members.


But wrappers can be made in a separate library. Why should they be included in the proposal? As long as the proposal allows them, a library only needs to be made once (and if not needed by someone, not used).
We need some probe of concept applied to concrete examples we want to solve. I believe a strong type feature must solve the builtin type case. If your proposal is not a strong type proposal, then forget my comments.

This is how I would practically solve the primitive types problem using my proposal:

------------------------------------------

// library.hpp

template <typename T>
class Wrap {
    public:
        using base_type = T;

        Wrap(const Wrap & other) : t_(other.t_) {}
        explicit Wrap(T t) : t_(t) {}
   
    private:
        T t_;
};

template <typename C>
class ConvertBack : using C {
    operator base_type() { return t_; }
};

template <typename C>
class Plus : using C {
    Plus operator+(Plus other) { return Plus{t_ + other.t_}; }
};

template <typename C>
class Minus : using C {
    Minus operator-(Minus other) { return Minus{t_ - other.t_}; }
};

template <typename C>
class Prod : using C {
    Prod operator+(Prod other) { return Prod{t_ * other.t_}; }
};

template <typename C>
class Div : using C {
    Div operator/(Div other) { return Div{t_ / other.t_}; }
};

template <typename T>
using Arithmetic = Div<Prod<Minus<Plus<Wrap<T>>>>>;

// .... and so on

// usercode.cpp

#include "library.hpp"

// Strong typedef of int with +,-,/,*
struct MyInt : using Arithmetic<int> {}

// Strong typedef of double with +,- convertible to double
struct MyDouble : using ConvertBack<Minus<Plus<Wrap<double>>>> {}

--------------------------------------------

If there's something missing you'd like to see how I'd do, let me know. Of course verbosity depends on granularity. If all operations need to be manually selected you'll have lots of classes. If the granularity required is less each class could add more operators at once.

Note that you need access to privates to be able to do this, so this could be an use-case for that.

A problem that is not tackled in this code and that we are also discussing is what to do with non-member functions (that could possibly take base_type as input). That depends on how non-member functions will be handled in the proposals. If they can be handled I would actually be happier.


Forcing the user to inherit to add more data adds inheritance, which means that types are now implicitly convertible (to at least a common base). I don't think this is desirable. I think strong typing should work on its own.
How then you will be able to construct a B from a A if B adds more non-static data members?

One option is to simply define a constructor in B which takes A. Otherwise, it would be the same case when you create a class with a member which the constructor does not initialize. Otherwise one exception could be made for constructors so that they can be redefined in the new class. This probably makes sense and it shoudn't be possible to break something (unless one creates a constructor which does not initialize some fields which are assumed initialized by the original class.. but then I believe it's a reasonable risk).


As you want to inherit all the member functions from the underlying class, it has a sens to concentrate on member functions. I don't understand why friend functions could not follow the same schema.

Well, friend functions are not members, so if you allow copying them, you are allowing copying arbitrary non-member functions (which I approve of). The only problem with this is that it is a big change - on top of this proposal. Just that.


void foo(int);

void foo(double) : using foo(int);

to use an analogous syntax to the one in my proposal. Not sure if this is clearer, let me know.
I believe I understand you now but I don't see how this could help.
 
Well, this would be the way to create the "trampolines" for non-member functions. When you copy a class, you specify just once from which class you are copying. The compiler can then apply the change to all member functions automatically.

However, you still have to specify non-member functions manually, since the compiler cannot assume you want them all.


and also to A (since C is a type-copy of a type-copy).
The first Opaque proposal contained a implicit subsumption relation that allowed that. I'm all for, but I believe the committee doesn't like transitivity in conversions.

Well, it is not really transitivity. As in, the C to A transition does not need an implicit transition from B. The user is explicitly creating the conversion operator. The data in C exists, so it can be copied directly. Maybe you mean transitivity in the sense that the compiler needs to remember that C is a copy of B in order for it to know that it is also a copy of A. In that case I agree.

In any case, I don't think it is really necessary, it was just an idea.

Best,
Eugenio Bargiacchi

--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/gkJUVnL-Fmg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.

Vicente J. Botet Escriba

ungelesen,
02.01.2017, 12:49:1802.01.17
an std-pr...@isocpp.org
Le 02/01/2017 à 13:31, Eugenio Bargiacchi a écrit :
Dear Vicente,

Eugenio, I believe the use cases we have both in mind are quite different, and so we don't reach to understand one to each other.

It seems so. I am available to try other forms of communications if you want (chat, voice call) if you want. I'm sorry I'm not being as clear as I want.
No problem. It is my fault also.


I have a question. Does you copy class proposal needs to have the definition of the copied class available?
If yes, I don't think this could be acceptable.
If the declaration is enough, the copied functions would need at least an internal conversion to/from the underlying type, isn't it?

My proposal does not need the definition. Yes, I suppose an internal conversion would be needed. The internal conversion would probably work similarly to how inheritance works, even if adding non-static members was allowed. As in, the compiler knows that the first piece of the class will be the same as the original, and every new member is added at the end.
So if we argree that internal conversions are needed from/to the underlying type, the new strong type couldn't add any new non-static data member :)

B b;
b.A::bar(5);

Agreed. And the language let me do that even if you find it could be bad practice. I don't see why your copy approach wouldn't let me do that.

In my approach the old function is impossible to hide, so I think that the two cases are different.
If in all these cases your answer is "the program will fail to compile, just copy the class by hand" then I think the feature would be severely limited in what it could actually do.
You are probably right, and the copy should copy every thing and give access to everything internally. I need some concrete examples to understand what this possibility will solve.

Well, for example, a library that wanted to create strong typedefs of primitive types via wrappers and only expose certain operations, without getters/setters ;) I've added some code later to show you how a possible implementation using this proposal.
If I understand correctly, you want to hide the original completely, and just be offered an easy way to bring back some of its interface (via trampolines) to its "wrapper", which would be the strong typedef.
I wouldn't say hiding, just don't looking inside the underlying type. I want to provide access to it (when needed) but only as a whole.
Possibly adding more public methods only using the public interface, which could be done just the same with non-member methods. The end result is a class that cannot be extended in any meaningful way using any of the normal C++ facilities, since every juicy bit is hidden.
Why it is unusable? The class provide access to everything you want.

Classes don't always come with getters and setters for every private field. Especially if the interface they provide is a high level one. It happens often in low-level code that you want to interact with the underlying bits in very specific ways, but you don't necessarily want to give that kind of access to users. So creating a strong typedef of such a class while adding a new mode of operation would be impossible, as the copy won't have access to the underlying implementation.

Another could be when you have to types that you know contain the same (private) data, but should expose different public interfaces. Then you could first create the class containing the data, and copy it two times, each time exposing a different interface (which would need access to the private data). Once again, I unfortunately don't have a practical use case at the moment. I just think it could be useful.

Still the code below is a good use-case I think, since it would be needed to provide strong typedefs of primitive types.
Applying strong-typing again to a strong-typed class would be useless as access to internals would be the same,
You can strong-type again. I don't see the trouble. E.g; you can have a energy strong-type and have kinetic_energy, potential_energy and  heat_energy strong-type of energy.
Typing and representation are two different things. Strong types are just that. Share a representation and have different types.

Yes, of course. What I mean is, you have created a class which can only create exact copies of it. If you need additional changes, it will probably be easier to strong-type the original again and create new trampolines from that. This is what I mean by non-extendable.
Why? I don't understand.

aside from possibly adding more data (if needed) in the public attributes, since only those are editable. This would also prevent encapsulation of new data. If not, it would just mean that the feature wouldn't be used in a composable way.
I believe now that strong-type shouldn't add any new non-static data member

I start to see what you mean. But still, if one could add them, the copy would still "contain" an equivalent of the original class at its beginning. It wouldn't be guaranteed reinterpret_cast-able, but the fields would be there anyway. The original representation is there - so to me it could still be considered a strong-typedef (stretching a bit the definition maybe).
Remember that you need conversions from both sides to implement the trampolines.


Is there any other reason why you don't like the idea?
No, but it is enough.
A feature that creates a non-extendable object is like a dead-end.
I must agree with your last sentence, but I don't think the strong-types I'm suggesting are non-extendable?

Well, if only the public interface is available, than adding new methods to it is pretty much equivalent with creating non-member functions (since those work with public interface). Then you might as well remove the ability to add new member functions, since non-members will do just fine and there's no need to have them as members.
People like member functions also ;-)


But wrappers can be made in a separate library. Why should they be included in the proposal? As long as the proposal allows them, a library only needs to be made once (and if not needed by someone, not used).
We need some probe of concept applied to concrete examples we want to solve. I believe a strong type feature must solve the builtin type case. If your proposal is not a strong type proposal, then forget my comments.

This is how I would practically solve the primitive types problem using my proposal:

------------------------------------------

// library.hpp

template <typename T>
class Wrap {
    public:
        using base_type = T;

        Wrap(const Wrap & other) : t_(other.t_) {}
        explicit Wrap(T t) : t_(t) {}
   
    private:
        T t_;
};

template <typename C>
class ConvertBack : using C {
public:

    operator base_type() { return t_; }
};

template <typename C>
class Plus : using C {
public:

    Plus operator+(Plus other) { return Plus{t_ + other.t_}; }
};

template <typename C>
class Minus : using C {
    Minus operator-(Minus other) { return Minus{t_ - other.t_}; }
};

template <typename C>
class Prod : using C {
    Prod operator+(Prod other) { return Prod{t_ * other.t_}; }
};

template <typename C>
class Div : using C {
    Div operator/(Div other) { return Div{t_ / other.t_}; }
};

template <typename T>
using Arithmetic = Div<Prod<Minus<Plus<Wrap<T>>>>>;

// .... and so on

// usercode.cpp

#include "library.hpp"

// Strong typedef of int with +,-,/,*
struct MyInt : using Arithmetic<int> {}

// Strong typedef of double with +,- convertible to double
struct MyDouble : using ConvertBack<Minus<Plus<Wrap<double>>>> {}

This example is already more complete. Thanks.
--------------------------------------------
ConvertBack will convert to Minus<Plus<Wrap<double>>> :(

Maybe the conversion should be part of Wrap or wrap should provide a underlying function as I have in my library.

operator+ returns Plus<Wrap<double>> but we want MyInt or MyDouble :(

See below how you could return the Final class.

If there's something missing you'd like to see how I'd do, let me know. Of course verbosity depends on granularity. If all operations need to be manually selected you'll have lots of classes. If the granularity required is less each class could add more operators at once.

Note that you need access to privates to be able to do this, so this could be an use-case for that.
I like the idea and yes it needs access to the private data. However if we want to be able to provide the conversion to the underlying type, it seems that the underlying data  is no more private anymore and should at least be read. To be fair, I have never created strong types for classes that are not builtin types, or that are not copied completely. If the function you want to add modifies only part of the underlying type then either the underlying type provides already this possibility, or the new type would need access to the private part as you propose.

I would need to see how I could adapt my Opaque library with this language feature and see if it could be more simple. I will try with private and without access.


A problem that is not tackled in this code and that we are also discussing is what to do with non-member functions (that could possibly take base_type as input). That depends on how non-member functions will be handled in the proposals. If they can be handled I would actually be happier.
template <typename Final, typename C>
class AFct : using C {
public:
    friend Final aFct(Final other) { return Final{aFct(other.t_)}; }
};

Forcing the user to inherit to add more data adds inheritance, which means that types are now implicitly convertible (to at least a common base). I don't think this is desirable. I think strong typing should work on its own.
How then you will be able to construct a B from a A if B adds more non-static data members?

One option is to simply define a constructor in B which takes A. Otherwise, it would be the same case when you create a class with a member which the constructor does not initialize. Otherwise one exception could be made for constructors so that they can be redefined in the new class. This probably makes sense and it shoudn't be possible to break something (unless one creates a constructor which does not initialize some fields which are assumed initialized by the original class.. but then I believe it's a reasonable risk).
I suggest you try it and see how it works. Let me know if it is reasonable.


As you want to inherit all the member functions from the underlying class, it has a sens to concentrate on member functions. I don't understand why friend functions could not follow the same schema.

Well, friend functions are not members, so if you allow copying them, you are allowing copying arbitrary non-member functions (which I approve of). The only problem with this is that it is a big change - on top of this proposal. Just that.
Not exactly. The compiler can copy the friend functions because it is aware of these functions and so it could copy them.


void foo(int);

void foo(double) : using foo(int);

to use an analogous syntax to the one in my proposal. Not sure if this is clearer, let me know.
I believe I understand you now but I don't see how this could help.
 
Well, this would be the way to create the "trampolines" for non-member functions. When you copy a class, you specify just once from which class you are copying. The compiler can then apply the change to all member functions automatically.

However, you still have to specify non-member functions manually, since the compiler cannot assume you want them all.
I believe you could let the user copy non-member non-friend function by itself.


and also to A (since C is a type-copy of a type-copy).
The first Opaque proposal contained a implicit subsumption relation that allowed that. I'm all for, but I believe the committee doesn't like transitivity in conversions.

Well, it is not really transitivity. As in, the C to A transition does not need an implicit transition from B. The user is explicitly creating the conversion operator. The data in C exists, so it can be copied directly. Maybe you mean transitivity in the sense that the compiler needs to remember that C is a copy of B in order for it to know that it is also a copy of A. In that case I agree.
Yes this is the case. You are requesting the compiler to do the conversion transitively.


Vicente

Eugenio Bargiacchi

ungelesen,
02.01.2017, 13:21:5502.01.17
an ISO C++ Standard - Future Proposals
Dear Vicente,

As in, the compiler knows that the first piece of the class will be the same as the original, and every new member is added at the end.
So if we argree that internal conversions are needed from/to the underlying type, the new strong type couldn't add any new non-static data member :)

I'm not sure I agree fully, but in any case the ability to add new non-static data members is not something I feel strongly about at the moment. It's something that can be added in a future proposal if there should arise a need. =)

Maybe I'll just remove this from the proposal for the moment.
Yes, of course. What I mean is, you have created a class which can only create exact copies of it. If you need additional changes, it will probably be easier to strong-type the original again and create new trampolines from that. This is what I mean by non-extendable.
Why? I don't understand.
 
It may be that I just have a different idea in my head on how p0109 would work in practice. I just have a feeling that those strong typedefs could be hard to work with, but I may just have misunderstood some parts.
I start to see what you mean. But still, if one could add them, the copy would still "contain" an equivalent of the original class at its beginning. It wouldn't be guaranteed reinterpret_cast-able, but the fields would be there anyway. The original representation is there - so to me it could still be considered a strong-typedef (stretching a bit the definition maybe).
Remember that you need conversions from both sides to implement the trampolines.

Well, in inheritance child classes can be converted to their parents automatically (even if they don't define a constructor that initializes any new data they might have). If that can be done I don't see why this would be impossible for a copied class + a new non-static data member to do the same thing.

Well, if only the public interface is available, than adding new methods to it is pretty much equivalent with creating non-member functions (since those work with public interface). Then you might as well remove the ability to add new member functions, since non-members will do just fine and there's no need to have them as members.
People like member functions also ;-)

Eh, true. But every feature must be justified by a use case! ;D (joking)

template <typename C>
class ConvertBack : using C {
public:

 Whoops!

// Strong typedef of double with +,- convertible to double
struct MyDouble : using ConvertBack<Minus<Plus<Wrap<double>>>> {}

This example is already more complete. Thanks.
--------------------------------------------
ConvertBack will convert to Minus<Plus<Wrap<double>>> :(

Maybe the conversion should be part of Wrap or wrap should provide a underlying function as I have in my library.

operator+ returns Plus<Wrap<double>> but we want MyInt or MyDouble :(

See below how you could return the Final class.
Not exactly!

I'll try to analyze MyDouble to explain how it works.

Wrap<double> I believe we both understand it. So now let's consider Plus<Wrap<double>>.

template <typename C>
struct Plus : using C { // changed to struct to avoid public

    Plus operator+(Plus other) { return Plus{t_ + other.t_}; }
};

So it copies the templated class, and adds the operator+ to it. So the class becomes the equivalent of:

struct Plus { // Equivalent of Plus<Wrap<double>>
    using base_type = double;

    Plus(const Plus & other) : t_(other.t_) {}
    explicit Plus(double t) : t_(t) {}

    Plus operator+(Plus other) { return Plus{t_ + other.t_}; }   
private:
        double t_;
};

See? Now any mention of Wrap has disappeared, since we copied that. Wrap<double>, the original class, has been replaced by Plus (since we are copying). After the minus, the same thing would happen:

struct Minus { // Equivalent of struct Minus<Plus<Wrap<double>>>
    using base_type = double;

    Minus(const Minus & other) : t_(other.t_) {}
    explicit Minus(double t) : t_(t) {}

    Minus operator+(Minus other) { return Minus{t_ + other.t_}; }

    Minus operator-(Minus other) { return Minus{t_ - other.t_}; } 
private:
        double t_;
};

And finally, the last step:

struct ConvertBack { // Equivalent of struct ConvertBack<Minus<Plus<Wrap<double>>>>
    using base_type = double;

   
ConvertBack(const ConvertBack & other) : t_(other.t_) {}
    explicit
ConvertBack(double t) : t_(t) {}

    // From Plus originally
    ConvertBack operator+(ConvertBack other) { return ConvertBack{t_ + other.t_}; }
    // From Minus originally
    ConvertBack operator-(ConvertBack other) { return ConvertBack{t_ - other.t_}; }
    // From ConvertBack, unchanged, but using the typedef (base_type) made initially in Wrap<double>
    operator base_type() { return t_; }
private:
        double t_;
};

I hope this makes more sense to you.


template <typename Final, typename C>
class AFct : using C {
public:
    friend Final aFct(Final other) { return Final{aFct(other.t_)}; }
};

This could be a way, yes.


One option is to simply define a constructor in B which takes A. Otherwise, it would be the same case when you create a class with a member which the constructor does not initialize. Otherwise one exception could be made for constructors so that they can be redefined in the new class. This probably makes sense and it shoudn't be possible to break something (unless one creates a constructor which does not initialize some fields which are assumed initialized by the original class.. but then I believe it's a reasonable risk).
I suggest you try it and see how it works. Let me know if it is reasonable.

I'll try to work on this a bit then.


Well, friend functions are not members, so if you allow copying them, you are allowing copying arbitrary non-member functions (which I approve of). The only problem with this is that it is a big change - on top of this proposal. Just that.
Not exactly. The compiler can copy the friend functions because it is aware of these functions and so it could copy them.

This seems reasonable.


However, you still have to specify non-member functions manually, since the compiler cannot assume you want them all.
I believe you could let the user copy non-member non-friend function by itself.

Yes. However, can you do this process on random non-member functions to change their parameters to other types? If not, why not? Are you constrained in doing this only in copied class and with functions taking/returning the original class, maybe? Why?

I'm not sure what the answers should be.


Well, it is not really transitivity. As in, the C to A transition does not need an implicit transition from B. The user is explicitly creating the conversion operator. The data in C exists, so it can be copied directly. Maybe you mean transitivity in the sense that the compiler needs to remember that C is a copy of B in order for it to know that it is also a copy of A. In that case I agree.
Yes this is the case. You are requesting the compiler to do the conversion transitively.

Makes sense. Then forget the idea =)

Best,
Eugenio

--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/gkJUVnL-Fmg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.

Vicente J. Botet Escriba

ungelesen,
02.01.2017, 17:36:3102.01.17
an std-pr...@isocpp.org
Le 02/01/2017 à 19:21, Eugenio Bargiacchi a écrit :
Dear Vicente,
As in, the compiler knows that the first piece of the class will be the same as the original, and every new member is added at the end.
So if we argree that internal conversions are needed from/to the underlying type, the new strong type couldn't add any new non-static data member :)

I'm not sure I agree fully, but in any case the ability to add new non-static data members is not something I feel strongly about at the moment. It's something that can be added in a future proposal if there should arise a need. =)

Maybe I'll just remove this from the proposal for the moment.
Great.


I start to see what you mean. But still, if one could add them, the copy would still "contain" an equivalent of the original class at its beginning. It wouldn't be guaranteed reinterpret_cast-able, but the fields would be there anyway. The original representation is there - so to me it could still be considered a strong-typedef (stretching a bit the definition maybe).
Remember that you need conversions from both sides to implement the trampolines.

Well, in inheritance child classes can be converted to their parents automatically (even if they don't define a constructor that initializes any new data they might have). If that can be done I don't see why this would be impossible for a copied class + a new non-static data member to do the same thing.
You need to construct a derived from a base. If derived contains more data you can not do it correctly.
Yes. I was missing all the copies and I mixed base_type as the base type of ConvertBack. Sorry for reading your code too quickly.

I start liking your feature more and more. It could really help to make easier to define opaque types.

The usage of the previous example is as if your copied classes were mixins of the copied base class adding some additional functions. Unfortunately we can not add more data due to the need for the double conversion.

Note how

template <typename C>
struct Plus : using C {
    Plus operator+(Plus other) { return Plus{t_ + other.t_}; }
};

is close to  p0109

template <typename C>
using Plus : private C {
    Plus operator+(Plus x, Plus y) = default;
};







However, you still have to specify non-member functions manually, since the compiler cannot assume you want them all.
I believe you could let the user copy non-member non-friend function by itself.

Yes. However, can you do this process on random non-member functions to change their parameters to other types?
IMO, you could change any function that has an underlying type as parameter or return type using the trampoline idea. The problem is just that the user needs to select the functions to default the generation of the trampoline generation. 
If not, why not? Are you constrained in doing this only in copied class and with functions taking/returning the original class, maybe? Why?
No. You could substitute any type T by a type U if U(T(u)) == u as the trampoline is doing just this double conversion. The particularity of opaque types is that the conversion costs nothing as both classes share the same representation. For other types, the cost of the conversions could be more visible.


I'm not sure what the answers should be.
Vicente

ionto...@gmail.com

ungelesen,
03.01.2017, 02:52:4903.01.17
an ISO C++ Standard - Future Proposals, sval...@gmail.com
What about a syntax like this:

struct Foo
{
};
typealias int int_;
typealias Foo Foo2;


On Monday, December 19, 2016 at 3:04:48 AM UTC-8, sval...@gmail.com wrote:
This is a stub proposal on strong typedefs, i.e. types that work in the exact same way, but allow separate overloading. Other papers and proposals exist, but I've tried a different approach that tries to mimic a more inheritance-like syntax which might be more intuitive. The full text can be found online at https://github.com/Svalorzen/CppCopyProposal.

I'm copying the text below. Thanks in advance for your comments.

Duplication and Extension of Existing Classes
=============================================

Introduction
------------

This document describes a possible approach to duplicate existing functionality
while wrapping it in a new type, without the burden of inheritance and to allow
function overloads on syntactically identical but semantically different types
(also known as *strong typedef*).

Alternatives
------------

### Typedef / Using Directive ###

Using a type alias creates an alternative name for a single type. However, this
leaves no space to implement overloads that are context-specific. Nor a type can
be extended in a simple way while keeping the old interface intact.

### Inheritance ###

Inheritance requires redefinition of all constructors, and creates a stricter
dependency between two classes than what is proposed here. Classes may be
converted to a common ancestor even though that is undesired or even dangerous
in case of implicit conversions.

Inheritance may also be unwanted in order to avoid risks linked to polymorphism
and freeing data structures where the base class does not have a virtual
destructor.

### Encapsulation with Manual Exposure of Needed Methods ###

This method obviously requires a great deal of code to be rewritten in order to
wrap every single method that the old class was exposing.

In addition one needs to have intimate knowledge of the original interface in
order to be able to duplicate it correctly. Template methods, rvalue references,
possibly undocumented methods which are required in order to allow the class to
behave in the same way as before. This heightens the bar significantly for many
users, since they may not know correctly how to duplicate an interface and how
to forward parameters to the old interface correctly.

The new code also must be maintained in case the old interface changes.

### Copying the Base Class ###

This can be useful, but requires all code to be duplicated, and thus
significantly increases the burden of maintaining the code. All bugs discovered
in one class must be fixed in the other class too. All new features applied to
one class must be applied to the other too.

### Macro-expansion ###

Macro expansions can be used in order to encode the interface and implementation
of a given class just one time, and used multiple times to produce separate
classes.

This approach is unfortunately not applicable to existing code, and is very hard
to extend if one wants to copy a class but add additional functionality to it.

### Templates ###

Templates produce for each instantiation a separate type. They are unfortunately
not applicable to previously existing code. For new code, they would require the
creation of "fake" template parameters that would need to vary in order to
produce separate types.

In addition, class extension through templates is not possible: variations would
need to be made through specialization, which itself requires copying existing
code.

Previous Work
-------------

Strong typedefs have already been proposed for the C++ language multiple times
([N1706](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1706.pdf),
[N1891](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1891.pdf),
[N3515](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3515.pdf),
[N3741](https://isocpp.org/files/papers/n3741.pdf)). These typedefs are named
*opaque typedefs*, and these papers try to explore and define exactly the
behavior that such typedefs should and would have when used to create new
types. In particular, the keywords `public`, `protected` and `private` are used
in order to create a specific relation with the original type and how is the
new type allowed to be cast back to the original type or be used in its place
during overloads.

This document shares many of the the same principles, for example (quoting from
N3741):

> - Consistent with restrictions imposed on analogous relationships such as
>   base classes underlying derived classes and integer types underlying enums,
>   an underlying type should be (1) complete and (2) not cv-qualified. We also do
>   not require that any enum type, reference type, array type, function type, or
>   pointer-to-member type be allowed as an underlying type.

However, this document tries to propose a possibly more simple approach, where
a new language feature is introduced with the same meaning and functionality as
if the user autonomously implemented a new class him/herself, matching the
original type completely. Thus, it should result for the user more simple to
understand (as it simply matches already the already understood mechanics of
creating a new, unique type from nothing), and no new rules for type conversion
and selection on overloads have to be created.

Syntax
------

### Simple Case ###

Syntax could look something like this:

```cpp
class Base {
    public:
        Base() : x(0) {}
        void foo() { std::cout << "foo " << x << "\n"; }
    private:
        int x;
};

struct Copy : using Base {};

`reinterpret_cast` may also be used to convert back to the original class,
limited by the tool's already existing rules.

In general the usual rules of `reinterpret_cast` apply to the copied classes
### Copying Template Classes ###

Since the construct is similar to inheritance, the syntax for creating aliases
of templated classes could be the same:

```cpp
template <typename T>
struct A {};

template <typename T>
struct B : using A<T> {};

B<int> b;
```

The copied class must have the same number or less of template parameters than
the base class. Partial or full specializations of the base class can be allowed:

```cpp
template <typename T, typename U>
struct A {};

template <typename T>
struct B : using A<T, double> {};

B<int> b;
```

When the base class has partial specializations, only those who apply are copied
to the copied class.

```cpp
template <typename T, typename U>
struct A { T t; U u; };

template <typename U>
struct A<double, U> { double y; U u; };

template <typename T>

struct A<T, int> { T t; char z; };

template <typename T>

struct B : using A<T, double> {};

/* Equivalent to

template <typename T>

struct B { T t; double u; };

template <>
struct B<double> { double y; double u; };

*/
```

The copied class can add additional specializations. Or specializations for a
given class can copy another.

```cpp
template <typename T>
struct A { int x; };

struct B { char c; };


template <typename T>
struct C : using A<T> {};

template <>
struct C<double> : using B {};

template <>
struct A<int> : using C<double> {};

/* Equivalent to

template<>
struct A<int> { char c; };

template <typename T>

struct C { int x; };

template <>
struct C<double> { char c; };

*/
```

### Copying Multiple Dependent Classes ###

Copying multiple classes using the simple syntax we have described can be
impossible if those classes depend on one another. This is because each copy
would depend on the originals, rather than on the copied classes. A possible way
to specify such dependencies could be:

```cpp
struct A;

struct B {
    A * a;
};

struct A {
    B b;
};

struct C;

struct D : using B {
    using class C = A;
};

struct C : using A {
    using class D = B;
};

/* Equivalent to

struct C;

struct D {
    C * a;
};

struct C {
    D b;
};

*/
```

`using class` has been used in order to disambiguate it from normal `using`
alias directive. `using class` is only valid when the left hand side has been
defined as a copy of the right hand side.

In case of a template base class using a template second class, one could
specify different copies for certain specializations;

```cpp
template <typename T>
struct A {};

template <typename T>
struct B {
    A<T> a;
};

template <typename T>
struct C : using A<T> {};

```

### Substituting Existing Functionality (Optional) ###

### Copying and Extending Primitive Types (Optional) ###

The same syntax could be used in order to extend primitive types. Using the
extension that allows the modification of the copied types, this could allow for
creation of numeric types where some operations are disabled as needed.

```cpp
struct Id : using int {
    Id operator+(Id, Id) = delete;
    Id operator*(Id, Id) = delete;
    // Non-explicitly deleted operators keep their validity

    // Defining new operators with the old type can allow interoperativity
    Id operator+(Id, int);
    // We can convert the copied type to the old one.
    operator int() { return (*this) * 2; }
};

/* Equivalent to

class Id final {
    public:
        Id operator/(Id lhs, Id rhs) { return Id{lhs.v_ / rhs.v_}; }
        Id operator-(Id lhs, Id rhs) { return Id{lhs.v_ - rhs.v_}; }

        Id operator+(Id, int);
        operator int() { return v_ * 2; }
    private:
        int v_;
};

*/
```

Note that when copying from a primitive types inheritance is forbidden as the
generated copy is `final` (although it is allowed to keep copying the newly
created class).

### STL Traits (Optional) ###

Traits could be included in the standard library in order to determine whether a
class is a copy of another, or if it has been derived from a copy
(copies/inheritances could be nested arbitrarily).

```cpp
struct Base {};

struct Copy : using Base {};

static_assert(std::is_copy<Copy, Base>::value);

struct ChildCopy : public Copy {};

struct CopyChildCopy : using ChildCopy {};

static_assert(std::is_copy_base_of<Base, CopyChildCopy>::value);
```

Eugenio Bargiacchi

ungelesen,
03.01.2017, 06:31:2603.01.17
an ISO C++ Standard - Future Proposals
Dear Vicente,

Well, in inheritance child classes can be converted to their parents automatically (even if they don't define a constructor that initializes any new data they might have). If that can be done I don't see why this would be impossible for a copied class + a new non-static data member to do the same thing.
You need to construct a derived from a base. If derived contains more data you can not do it correctly.

Well not necessarily. There are classes that take fewer parameters than the number of their members. There are classes that define a constructor with no parameters, even though they have members. There are structs with no constructor at all.

For example, if you copy a class and add, say, an std::vector, I'd say it would be very easy to create the copy from the original, since the std::vector can initialize itself very easily.


Yes. I was missing all the copies and I mixed base_type as the base type of ConvertBack. Sorry for reading your code too quickly.

I start liking your feature more and more. It could really help to make easier to define opaque types.

I'm really glad you like it!


Note how

template <typename C>
struct Plus : using C {
    Plus operator+(Plus other) { return Plus{t_ + other.t_}; }
};

is close to  p0109

template <typename C>
using Plus : private C {
    Plus operator+(Plus x, Plus y) = default;
};
 
Yes. However, if non-member functions can be copied, I believe there should exist a syntax to create them also outside the copied class. Suppose:

// From library
struct A {};
struct B : using A {}

// User defined
void foo(A a) {}
// How to define foo(B)?

The problem with this example is that when foo(A) is defined, B has already been defined. The original case in p0109 is "easy" in the sense that the non-member operators for primitive types are always defined before copies can be made, so they can surely be seen in the copy.

One could argue that such a problem does not exist, since the new function can be made template from the start and so apply to both A and B. Perhaps. I'm just asking the question out of completeness, I'm not sure what the answer should be.


If not, why not? Are you constrained in doing this only in copied class and with functions taking/returning the original class, maybe? Why?
No. You could substitute any type T by a type U if U(T(u)) == u as the trampoline is doing just this double conversion. The particularity of opaque types is that the conversion costs nothing as both classes share the same representation. For other types, the cost of the conversions could be more visible.

I'm not sure that definition always holds, unfortunately. You'd have to force them to have the same public interface, or else maybe the conversion from U->T->U will work, but T has a different interface so they are not compatible. Consider:

struct A {
    A() : x_(0) {}
    int x_;
};

struct B {
    B(A a) : d_(5), i_(a.x_) {}
    operator A() { return A{i_}; }  
   
    double d_;
    int i_;
};

double foo(B b) {
    return b.d_ + b.i_;
}

If you just follow the conversion rules, then you could convert foo(B) into foo(A), since A(B(a)) == a for any a. However, it would fail to compile as of course B and A do not have the same interface. You'd have to guarantee that when converting A to B that A has at least the same interface as B (which is the same as copying, pretty much, even though it does not require it since one could do this by hand). This includes static members, typedefs, etc etc. It would also include non-member functions possibly - if foo uses them.

The only way to practically make that check would be to just let the compiler try, probably. Which would be equivalent to transforming foo into a templated function. Which I'm not sure is going to get accepted.

Best,
Eugenio

Vicente J. Botet Escriba

ungelesen,
03.01.2017, 12:29:2103.01.17
an std-pr...@isocpp.org
p0109 manages with classes as underling types also.


One could argue that such a problem does not exist, since the new function can be made template from the start and so apply to both A and B. Perhaps. I'm just asking the question out of completeness, I'm not sure what the answer should be.
If B is copied later the following could be a possibility

struct B : using A {
    friend void foo(B b) = default;
};

Otherwise the user can do just:

    void foo(B b)  { foo(A(b)); };

I don't know if it is worth providing something else to take care of this function copy.
Maybe

    void foo(B b) = default;



If not, why not? Are you constrained in doing this only in copied class and with functions taking/returning the original class, maybe? Why?
No. You could substitute any type T by a type U if U(T(u)) == u as the trampoline is doing just this double conversion. The particularity of opaque types is that the conversion costs nothing as both classes share the same representation. For other types, the cost of the conversions could be more visible.

I'm not sure that definition always holds, unfortunately. You'd have to force them to have the same public interface, or else maybe the conversion from U->T->U will work, but T has a different interface so they are not compatible. Consider:

struct A {
    A() : x_(0) {}
    int x_;
};

struct B {
    B(A a) : d_(5), i_(a.x_) {}
    operator A() { return A{i_}; }  
   
    double d_;
    int i_;
};

double foo(B b) {
    return b.d_ + b.i_;
}


If you just follow the conversion rules, then you could convert foo(B) into foo(A), since A(B(a)) == a for any a.
Right.
However, it would fail to compile as of course B and A do not have the same interface. You'd have to guarantee that when converting A to B that A has at least the same interface as B (which is the same as copying, pretty much, even though it does not require it since one could do this by hand). This includes static members, typedefs, etc etc. It would also include non-member functions possibly - if foo uses them.

You lost me. My copy is not the same as yours. I don't copy the function code, as I don't have access to it. It is not a template at all. The copied function is wrapping the existing functions.

double foo(A a) {
    return foo(B(a);
}

When one type is the opaque type of the other the conversion is trivial and so there is no need of trampoline in realiyt. Only the compiler must check the types and call to the base function.


The only way to practically make that check would be to just let the compiler try, probably. Which would be equivalent to transforming foo into a templated function. Which I'm not sure is going to get accepted.


Vicente

Eugenio Bargiacchi

ungelesen,
03.01.2017, 13:21:3603.01.17
an ISO C++ Standard - Future Proposals
Dear Vicente,


The problem with this example is that when foo(A) is defined, B has already been defined. The original case in p0109 is "easy" in the sense that the non-member operators for primitive types are always defined before copies can be made, so they can surely be seen in the copy.

p0109 manages with classes as underling types also.

Yes, of course. I meant the energy example.


One could argue that such a problem does not exist, since the new function can be made template from the start and so apply to both A and B. Perhaps. I'm just asking the question out of completeness, I'm not sure what the answer should be.
If B is copied later the following could be a possibility

struct B : using A {
    friend void foo(B b) = default;
};

This I like, although it does not really need to be a friend, does it?


Otherwise the user can do just:

    void foo(B b)  { foo(A(b)); };

This won't do, as we don't necessarily want B to be convertible to A. There should be something like copy_cast<A>(b), but I don't like it.


Maybe

    void foo(B b) = default;

 This could work but it has the potential to be unclear, as this definition could happen far away from B so it has less context. Maybe just restricting us to the case where all needed non-member functions have already been declared is still enough. I would do that for the moment.


You lost me. My copy is not the same as yours. I don't copy the function code, as I don't have access to it. It is not a template at all. The copied function is wrapping the existing functions.

double foo(A a) {
    return foo(B(a);
}

If A is convertible to B, you don't need a new way to declare a trampoline at all. The lines you wrote are enough (and I think also short enough), no new syntax needs to be introduced. It wouldn't matter if A is a copy of B or not.

If A is not directly convertible to B, what I said stands.

If you want to assume that B and A have the same underlying representation, you could try casting them bit by bit. But this would be equivalent to

double foo(A a) {
    return foo(*reinterpret_cast<B*>(a));
}

which is undefined behaviour in 90% of cases, even if the two classes are literally identical (as they must be standard_layout for it to be valid in C++, for some reason). So this is not going to work either. As in:

struct A {
    int a;
    private:
        int b;
};

struct B {
    int a;
    private:
        int b;
};

A a;
auto b = *reinterpret_cast<B*>(a); // undefined behaviour

This is undefined behaviour by the current standard. This proposal (and assume also p0109) rests on the assumption that this undefined behaviour can be relaxed when we explicitly define a class to be a copy of another.

Otherwise you'd have to first try a proposal to change how reinterpret_cast work. However, as it's behaviour is different from C, I assume it has been deliberately modified for some reason (which I don't know), so it probably won't be simple.

Best,
Eugenio

--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/gkJUVnL-Fmg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.

Alex Newlifer

ungelesen,
20.07.2017, 05:25:5320.07.17
an ISO C++ Standard - Future Proposals, sval...@gmail.com
Hello!

I suggest to use `explicit` specifier for using-declaration to make strong
typedef (aka completely new type).

Simple example:
```
using explicit my_int_t = int;

/* Now we cannot pass into function foo int values without explicit cast.
*/
void foo(my_int_t val)
{}

int val = 0;

foo(val); // Error
foo(static_cast<my_int_t>(val)); // Ok
foo(my_int_t(0)); // Ok
```


понедельник, 19 декабря 2016 г., 14:04:48 UTC+3 пользователь sval...@gmail.com написал:

Michał Dominiak

ungelesen,
20.07.2017, 05:28:1320.07.17
an ISO C++ Standard - Future Proposals, sval...@gmail.com
I don't think the syntax is the main problem of this proposed direction; write a paper with semantics described and publish it in a mailing.

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

Jonathan Müller

ungelesen,
20.07.2017, 05:29:4620.07.17
an std-pr...@isocpp.org
On 20.07.2017 11:25, Alex Newlifer wrote:
> Hello!
>
> I suggest to use `explicit` specifier for using-declaration to make strong
> typedef (aka completely new type).
>
> Simple example:
> ```
> using explicit my_int_t = int;
>
> /* Now we cannot pass into function foo int values without explicit cast.
> */
> void foo(my_int_t val)
> {}
>
> int val = 0;
>
> foo(val); // Error
> foo(static_cast<my_int_t>(val)); // Ok
> foo(my_int_t(0)); // Ok
> ```
>

While this would be a great step forward, this is not useful for all cases.
In a strong typedef you'd want to keep certain functions like an
`operator+` in your example, but not necessarily *all* operators, member
functions.

See my blog post about it for rationale:
http://foonathan.net/blog/2016/10/19/strong-typedefs.html

John McFarlane

ungelesen,
20.07.2017, 06:59:4320.07.17
an std-pr...@isocpp.org
On Thu, Jul 20, 2017 at 2:29 AM Jonathan Müller <jonathanm...@gmail.com> wrote:
On 20.07.2017 11:25, Alex Newlifer wrote:
> Hello!
>
> I suggest to use `explicit` specifier for using-declaration to make strong
> typedef (aka completely new type).
>
> Simple example:
> ```
> using explicit my_int_t = int;
>
> /* Now we cannot pass into function foo int values without explicit cast.
> */
> void foo(my_int_t val)
> {}
>
> int val = 0;
>
> foo(val); // Error
> foo(static_cast<my_int_t>(val)); // Ok
> foo(my_int_t(0)); // Ok
> ```
>

While this would be a great step forward, this is not useful for all cases.

Yes, something like this could be helpful for improving type safety.

In a strong typedef you'd want to keep certain functions like an
`operator+` in your example, but not necessarily *all* operators, member
functions.

Perhaps removing the operators of a type should be separated from the concern of removing implicit casting.  If you don't want the full complement of arithmetic, maybe you should not start with `int` at all, but instead 'explicitly alias' a type with only the operators you want.  (Something like `boost::operators` can make this a easier to achieve.) 

John

See my blog post about it for rationale:
http://foonathan.net/blog/2016/10/19/strong-typedefs.html

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

drozdovk...@gmail.com

ungelesen,
20.07.2017, 08:37:0220.07.17
an ISO C++ Standard - Future Proposals, sval...@gmail.com
Here is some ideas:

1. Strong typedefs are declared by 'using explicit alias_type = type'.
2. Strong typedefs can not be implicitly casted but can be explicitly casted.
3. Strong typedefs are different types by mean of template arguments.
4. Strong typedefs are guaranteed to be exactly the same in-memory and are exactly the same type by mean of RTTI (this requires some review over std::variant and other typeinfo-dependent templates).
5. Strong typedefs has helper code like

namespace std {
   template <typename T1, typename T2>
   enable_if<is_strong_alias<T1, T2>::value, T1&>::type explicit_cast(T2 & ref) { return static_cast<T1&>(ref); };
   //...
}

drozdovk...@gmail.com

ungelesen,
20.07.2017, 08:56:1520.07.17
an ISO C++ Standard - Future Proposals
Basically with 'using explicit' syntax we want to declare exactly the same runtime type without any operators or functions being modified with an exception that every use of self type will be treated as a use of the alias_type.

Basically this is something like declaring the original type as a template with typename Scope that is never used in class declaration and ignored by RTTI.

Tony V E

ungelesen,
20.07.2017, 15:28:5520.07.17
an Standard Proposals
Here's some example code which may or may not be helped by some form of "strong typedef".


It wraps a string or int or whatever, and makes it a unique type (based on a Tag template param).
Since it is for IDs, it doesn't want arithmetic (or concatenation) operations, but it does want equality and less (et al).

I suspect you always want to "inherit" == and !=, and most likely relational ordering.


To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.

--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.

To post to this group, send email to std-pr...@isocpp.org.

Zach Laine

ungelesen,
20.07.2017, 16:13:4720.07.17
an std-pr...@isocpp.org
Another common use case is x,y coordinates passed as arguments in GUI code:

move_window(y, x); // oops!

If x and y are typed, I get to detect such errors at compile time.  Cool -- ship it!

The only problem with this is that I also want to use x and y in a bit of math to determine where the window goes, so I need all the arithmetic *and* comparison operators.  Choosing which strong-typedefs get which operations is the real challenge any such feature needs to overcome.

In one GUI system I used to maintain, I made such strong typedefs for int window positions, but much of my math also included floating point operations on intermediate values.  You can imagine what a sticky wicket this became.

Zach

Nicol Bolas

ungelesen,
20.07.2017, 20:10:4520.07.17
an ISO C++ Standard - Future Proposals
On Thursday, July 20, 2017 at 4:13:47 PM UTC-4, Zach Laine wrote:
Another common use case is x,y coordinates passed as arguments in GUI code:

Couldn't you just do that with typed integers? That is, a template that uses a typename as the "type", which only allows math between values with the same typename, and no implicit conversions between values with different typenames? I don't see why you would need an alias at all.

Zach Laine

ungelesen,
20.07.2017, 20:41:3020.07.17
an std-pr...@isocpp.org
Right, and that's what I did.  My understanding of the language feature proposed is that I would have very simple syntax for specifying such types, rather than having to write them from scratch.

The point of my post is to point out that that's not easy.

Zach

Eugenio Bargiacchi

ungelesen,
21.07.2017, 04:08:2221.07.17
an ISO C++ Standard - Future Proposals
The syntax I have proposed at the beginning of the overall thread would allow you to specify with arbitrary precision the types you need, and once that is done require little to no additional code to create multiple type copies of the one you have specified.

I am currently working on a Clang patch to showcase a subset of the features I have proposed. It's not missing much, but unfortunately I've had little time to work on it in the past few months. Once that's done I'll post here, along with an example small library to show how my proposal could be used and to receive more feedback.

Eugenio

--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/gkJUVnL-Fmg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposals+unsubscribe@isocpp.org.

To post to this group, send email to std-pr...@isocpp.org.

Dejan Milosavljevic

ungelesen,
22.07.2017, 08:35:1622.07.17
an std-pr...@isocpp.org
In Konstantin Drozdov's list I'd like to add two more items( questions ).

We have (1)'inheritance' problem with strog typdefs'. What to do with unwanted property of base type.
Inherit all then delete or inherit nothing then extend.  What is inheritance in this problem?


Here is example of inherit all then delete.

Fine tuning( removing operators or functions ) of strong typedefs might look like this:
 
// Definition:
 
using explicit my_string_t = std::string; //!< "Inherit" all and everything.
 
// delete. Applies ONLY to strong typedef.
 
std::string operator+( my_string_t const& left, my_string_t const& right )delete;
ostream& operator<< (ostream& os, my_string_t const& str)delete ;
 
 
This rise question: (2)How far with tuning we may go?
Hera is example:
my_string_t my_s;
sort::sort( my_s.begin(), my_s.end() ); //!< Is this deletable??

void sort::sort( my_string_t::iterator, my_string_t::iterator )delete;
template <class Compare>
    void sort::sort( my_string_t::iterator, my_string_t::iterator, Compare comp )delete;

 Now we have weak( anything related with base type ) and strong( only direct usage of base type ) tuning.

--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.

To post to this group, send email to std-pr...@isocpp.org.

Nicol Bolas

ungelesen,
22.07.2017, 10:41:2322.07.17
an ISO C++ Standard - Future Proposals, sval...@gmail.com, drozdovk...@gmail.com
On Thursday, July 20, 2017 at 8:37:02 AM UTC-4, drozdovk...@gmail.com wrote:
Here is some ideas:

1. Strong typedefs are declared by 'using explicit alias_type = type'.
2. Strong typedefs can not be implicitly casted but can be explicitly casted.
3. Strong typedefs are different types by mean of template arguments.
4. Strong typedefs are guaranteed to be exactly the same in-memory and are exactly the same type by mean of RTTI (this requires some review over std::variant and other typeinfo-dependent templates).

So "strong" types are different for the purposes of template arguments. But they're not different for the purposes of RTTI? So, that means that this will be allowed:

using explicit alias = int;
any a
(in_place_t<alias>, alias(5));
int i = any_cast<int>(a);

`any_cast`'s checking is based on `type_info` extracted via RTTI. But `any`'s constructor is based on template instantiation; `in_place_t<alias>` is a different type from `in_place_t<int>`. It is incredibly silly for the following to be the case:

auto it1 = typeid(in_place_t<alias>);
auto it2 = typeid(in_place_t<int>);
auto i1 = typeid(alias);
auto i2 = typeid(int);

assert(it1 == it2); //fails
assert(i1 == i2); //succeeds

No, if the types are different, then the types are different. The whole point of a strong typedef is that it is a different type. So it should be a different type in all ways that are detectable. We can have a query to detect the underlying type of the alias. But as far as the actual type is concerned, it ought to be different in all things.
Allen antworten
Antwort an Autor
Weiterleiten
0 neue Nachrichten