struct A {
using Self = A;
};
struct B : using A {};
struct A {
using Self = A;
A& operator=(const Self&) { return *this; }
};
struct B : using A {};
--
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.
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.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAHfn%3D%2Bsw7zmU8iz7Ysc3e7cpvf3GdrZW32x0pGggvMP%3DFOY8xQ%40mail.gmail.com.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAOU91OPx4SQ_dLiHnpmXXqj69n7tW%3DOwOb60dXksnWWt8UjViA%40mail.gmail.com.
--
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/73728cbb-8905-31ef-e7f3-0dc8aeea22fb%40honermann.net.
Joël, about copy I feel the same way. I'll try to work out a nicer terminology.
--
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/CAOU91OOQe0Cq1QS1bR1X0EsWDWZebChkjoT3Dw7%2Bp9H6AAg_tw%40mail.gmail.com.
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 longas type members are all considered exported.
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?
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 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.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAHfn%3D%2Bvo2KXEWQHWQY6qB10oGabvZ9jGai3CtWNseLdA7QMNkw%40mail.gmail.com.
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 longas 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.
--
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.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CACGiwhH-rn1AfPMxmcyC7T4DzBXtAYa5gqms7hr1QGX0VLEvvg%40mail.gmail.com.
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.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAOU91ONWWCLw_vCxNryKEdgA4W%2BdLr979ZkPDLh7junqx_DETQ%40mail.gmail.com.
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.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.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 again, if you feel it would be best to think about this and try to add it, I'll definitely try to.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.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAHfn%3D%2BvYTB4zJT7zsQ8chPRJCj7m%3DP3yv1v_tMREZjfr%3DeSFpA%40mail.gmail.com.
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
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> {};
```
### 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 {
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?
/* 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);
```
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?
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
If the base class change, the strong types depending on it could need maintenance also, isn't it?
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.
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
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;
Why do you introduce this restriction? (on template parameter numbers)
In ** above C has not yet defined 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.
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?
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 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.
Why this restriction is needed or desirable. (w.r.t. final in type-copies of primitive types)
Example of where this can be useful welcome.
How other type traits behave for the copied class (see p0109r0)?
--
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/9574d1c1-b3ff-b9ae-ceee-908a2b983278%40wanadoo.fr.
- 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.- 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.There are many differences between my proposal and the Opaque proposal. I believe that the main ones that this proposal brings are: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?
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!
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.
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.
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.
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.
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.
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 know what do you mean by type-aliases of methods. Could you elaborate?
Could you elaborate? (on recursive conversion operator)
Why?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.
I don't know compiler writers would like to look ahead. I believe that C should be forward declared as a copy.
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.
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)
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 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?
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.
Features must be added to solve concrete problems.
I suggest you to work on the motivation section with real concrete examples.
--
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/f4a57116-c65b-94f4-51f2-e5cf8b4ff3ca%40wanadoo.fr.
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.
Why?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.
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."
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAHfn%3D%2BtVBKZOMtpURNBDCEJijFL_J5s%2BROrLwJfkRgGyWzZMgg%40mail.gmail.com.
Eugenio, please, preserve some additional context in your responses.
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.
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);
};
Right.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.
The Base conversion can be protected or private. The trampoline would take care of this conversions.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).
Hmm, using the public interface or don't.How are you going to print a copy?
I don't see the use case. Maybe you have one.How can you convert in a custom way to the original class?
If we need to access to private I believe it is worth creating a new class.How are you going to implement additional and useful methods without access to privates?
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.
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.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?
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.
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 believed this was already part of your proposal. (***)In some sense creating a new foo overload using the type-copy. However, I believe this would be a pretty big change.
Sorry, I don't know yet what are type-aliases of methods.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.
I'm lost.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 would say, yes. The question p0109 raise is whether the user wants it.Is it really wise to allow the compiler to be able to default convert B to A?
Maybe, but the C++ standard doesn't have constrains on whether the class is derived from another class.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 you need something like override to state clearly that you want to redefine the inherited behavior.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.
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.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAHfn%3D%2BtVBKZOMtpURNBDCEJijFL_J5s%2BROrLwJfkRgGyWzZMgg%40mail.gmail.com.
--
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/ef8190c7-f46f-cf76-7e45-2114a84eae27%40wanadoo.fr.
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.
Right.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.
The Base conversion can be protected or private. The trampoline would take care of this conversions.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).
Hmm, using the public interface or don't.How are you going to print a copy?
I don't see the use case. Maybe you have one.How can you convert in a custom way to the original class?
If we need to access to private I believe it is worth creating a new class.How are you going to implement additional and useful methods without access to privates?
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).
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.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?
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.
I believed this was already part of your proposal. (***)In some sense creating a new foo overload using the type-copy. However, I believe this would be a pretty big change.
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.
Sorry, I don't know yet what are type-aliases of methods.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.
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'm lost.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.
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).
I would say, yes. The question p0109 raise is whether the user wants it.Is it really wise to allow the compiler to be able to default convert B to A?
I'm not sure, however to me there's no difference either way.
Maybe, but the C++ standard doesn't have constrains on whether the class is derived from another class.Maybe. I thought that inherited classes are also declared without mentioning that they inherit, so I though that the same should hold for copies.
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 you need something like override to state clearly that you want to redefine the inherited behavior.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.
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.
Vicente
Best,
Eugenio
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?
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.B b;
b.A::bar(5);
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.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 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.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.
Why it is unusable? The class provide access to everything you want.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.
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.Applying strong-typing again to a strong-typed class would be useless as access to internals would be the same,
Typing and representation are two different things. Strong types are just that. Share a representation and have different types.
I believe now that strong-type shouldn't add any new non-static data memberaside 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 must agree with your last sentence, but I don't think the strong-types I'm suggesting are non-extendable?A feature that creates a non-extendable object is like a dead-end.
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.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).
How then you will be able to construct a B from a A if B adds more non-static data members?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.
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.
I believe I understand you now but I don't see how this could help.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.
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.and also to A (since C is a type-copy of a type-copy).
--
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/9adc5a02-3209-bf63-a684-fb0272da56e0%40wanadoo.fr.
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.
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.B b;
b.A::bar(5);
In my approach the old function is impossible to hide, so I think that the two cases are different.
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.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.
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.
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.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.
Why it is unusable? The class provide access to everything you want.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.
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.
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.Applying strong-typing again to a strong-typed class would be useless as access to internals would be the same,
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.
I believe now that strong-type shouldn't add any new non-static data memberaside 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 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?
I must agree with your last sentence, but I don't think the strong-types I'm suggesting are non-extendable?A feature that creates a non-extendable object is like a dead-end.
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.
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.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).
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>>>> {}
ConvertBack will convert to 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.
How then you will be able to construct a B from a A if B adds more non-static data members?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.
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.
I believe I understand you now but I don't see how this could help.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.
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.
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.and also to A (since C is a type-copy of a type-copy).
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.
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 :)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.
Why? I don't understand.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.
Remember that you need conversions from both sides to implement the trampolines.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).
People like member functions also ;-)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.
public:template <typename C>
class ConvertBack : using C {
This example is already more complete. Thanks.// Strong typedef of double with +,- convertible to double
struct MyDouble : using ConvertBack<Minus<Plus<Wrap<double>>>> {}
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.
template <typename Final, typename C>
class AFct : using C {
public:
friend Final aFct(Final other) { return Final{aFct(other.t_)}; }
};
I suggest you try it and see how it works. Let me know if it is reasonable.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).
Not exactly. The compiler can copy the friend functions because it is aware of these functions and so it could copy them.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.
I believe you could let the user copy non-member non-friend function by itself.However, you still have to specify non-member functions manually, since the compiler cannot assume you want them all.
Yes this is the case. You are requesting the compiler to do the conversion transitively.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.
--
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/edb42928-1e58-ee22-79ce-0fedd07247f1%40wanadoo.fr.
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.
Remember that you need conversions from both sides to implement the trampolines.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).
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.
I believe you could let the user copy non-member non-friend function by itself.However, you still have to specify non-member functions manually, since the compiler cannot assume you want them all.
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.
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);
```
You need to construct a derived from a base. If derived contains more data you can not do it correctly.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.
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.
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;
};
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.If not, why not? Are you constrained in doing this only in copied class and with functions taking/returning the original class, maybe? Why?
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.
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.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 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.
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.
If B is copied later the following could be a possibilityOne 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.
struct B : using A {
friend void foo(B b) = default;
};
Otherwise the user can do just:
void foo(B b) { foo(A(b)); };
Maybe
void foo(B b) = default;
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);}
--
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/44d44d44-483e-1ee5-90a7-1a4b966858f5%40wanadoo.fr.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/6718363e-3517-4af2-aa5f-9f0dd436c14b%40isocpp.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
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/95d6f9b7-2f4c-4b1e-6e29-477a9cb3a66d%40gmail.com.
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.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/95d6f9b7-2f4c-4b1e-6e29-477a9cb3a66d%40gmail.com.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+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/CABPJVnR1kzLAp3c-oLZBStoAmSjODiCAeq9MzDooq%2BTkzsV9sA%40mail.gmail.com.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAOHCbit4n0oyoGp0ymu70APJg_UuNYyxd%2Bt1FQoU96DMmh8JKw%40mail.gmail.com.
Another common use case is x,y coordinates passed as arguments in GUI code:
--
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/CALOpkJCC4VALGREnbQs9YSiWiPTFpYKHB4H7W3-gc28fs3QRvQ%40mail.gmail.com.
using explicit my_string_t = std::string; //!< "Inherit" all and everything.
std::string operator+( my_string_t const& left, my_string_t const& right )delete;ostream& operator<< (ostream& os, my_string_t const& str)delete ;
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;
--
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.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAHfn%3D%2BvqHeVqPYykUjYWH%2B-kF0PO_Y7e5sHsLFBVmH_kWefz_g%40mail.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).
using explicit alias = int;
any a(in_place_t<alias>, alias(5));
int i = any_cast<int>(a);
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