N1891: Progress toward Opaque Typedefs for C++0X

789 views
Skip to first unread message

Vicente J. Botet Escriba

unread,
Nov 19, 2012, 8:16:02 PM11/19/12
to std-pr...@isocpp.org
Hi,

there is an old proposal [1] from Walter E. Brown about opaque types that I have found quite interesting.

One of the motivating examples is the ability to overload on these new types


typedef public  double X, Y, Z; // Cartesian 3D coordinate types
typedef public double Rho, Theta, Phi; // polar 3D coordinate types
class PhysicsVector
{
public:
  PhysicsVector(X, Y, Z);
  PhysicsVector(Rho, Theta, Phi);
...
}; // PhysicsVector

Could some one tell me if the committee was interested on this proposal? Is there a plant to resurrect it?

Alisdair Meredith showed in [2] that forward constructor could help on this but that there are some issues that are not addressed by this construction.

struct MyType : std::string {
  using string::string;
};

I'm wondering if the syntax couldn't be adapted so that the new type could contain more function members.

class Address using public std::string {
public:
  
};
Note that there is no need to add forward constructors as the using keyword would intend already to have the same interface as the underlying type. 

If Address should not be seen implicitly as a std::string the using should be private

class Address using private std::string {
  // possibility to add new member functions
};

Note that builtin types could also be used as underlying types as it is the case of the Opaque typedef proposal.

class Temperature using double {
  // possibility to add new member functions
};

By default the new type has the same operations than the underlying type. Restricting the interface of the underlying type should also be possible if the operation is decorated with delete.

struct Address using std::string {
  std::size_t length() const = delete;
};

One question is whether the new class can add new data members. I don't think tis is a good idea the new type and the underlying type should have the same size.

An alternative could be to use a new keyword (e.g. alias) so that this constraint is clearer.

alias Address using std::string {
  std::size_t length() const = delete;
};

Is there some interest?

Vicente

[1] N1891: Progress toward Opaque Typedefs for C++0X

[2] N2141: Strong Typedefs in C++09(Revisited)

Martin Desharnais

unread,
Nov 19, 2012, 9:59:13 PM11/19/12
to std-pr...@isocpp.org
Alternativly, the alias declaration could be expand to support the explicit specifier:

#include <vector>

using A = int;
using explicit B = int;
template<typename T> using C = std::vector<T>;
template<typename T> using explicit D = std::vector<T>;

int main()
{
   
int n = 0;
    std
::vector<int> v = { 1, 2, 3 };

    A a
= n; // Implicit conversion
    B b
= n; // Error, no implicit conversion
    C
<int> c = v; // Implicit conversion
    D
<int> d = v; // Error, no implicit conversion
}

I think that would be consistent with explicit constructor and explicit conversion operator in that it disallow implicit conversion but stil let the programmer explicitly ask it.

--
Martin

Nicol Bolas

unread,
Nov 19, 2012, 10:41:46 PM11/19/12
to std-pr...@isocpp.org

I like the syntax here, particularly only allowing it with the new `using` syntax instead of the old `typedef`. However, `enum class` already does something similar with enumerators. So we might want to use syntax like that instead of `explicit`:

using A = int;
using class B = int;

template<typename T> using C = std::vector<T>;
template<typename T> using class D = std::vector<T>;

Admittedly that's bikesheding. I do like the `class` being there, which helps show that you're not creating a type alias but a new type.

Vicente J. Botet Escriba

unread,
Nov 20, 2012, 2:04:26 AM11/20/12
to std-pr...@isocpp.org
Le 20/11/12 04:41, Nicol Bolas a écrit :


On Monday, November 19, 2012 6:59:15 PM UTC-8, Martin Desharnais wrote:
Alternativly, the alias declaration could be expand to support the explicit specifier:

#include <vector>

using A = int;
using explicit B = int;
template<typename T> using C = std::vector<T>;
template<typename T> using explicit D = std::vector<T>;

int main()
{
   
int n = 0;
    std
::vector<int> v = { 1, 2, 3 };

    A a
= n; // Implicit conversion
    B b
= n; // Error, no implicit conversion
    C
<int> c = v; // Implicit conversion
    D
<int> d = v; // Error, no implicit conversion
}

I think that would be consistent with explicit constructor and explicit conversion operator in that it disallow implicit conversion but stil let the programmer explicitly ask it.

I like the syntax here, particularly only allowing it with the new `using` syntax instead of the old `typedef`.

Yes unifying proposals would be great. Please, could you point me to the new using proposal?

However, `enum class` already does something similar with enumerators. So we might want to use syntax like that instead of `explicit`:

using A = int;
using class B = int;
template<typename T> using C = std::vector<T>;
template<typename T> using class D = std::vector<T>;

Admittedly that's bikesheding. I do like the `class` being there, which helps show that you're not creating a type alias but a new type.
  
One of the major concerns of the Opaque proposals was to make the following difference:
Opaque typedef are explicitly convertible to the underlying type. But are they implicitly convertible? It depends. 
  • public opaque : implicit conversion to the underlying type
  • private opaque : explicit conversion to the underlying type
How the new syntax could take in account this point?
Does the new using proposal allows to add new function members?

Best,
Vicente

Nicol Bolas

unread,
Nov 20, 2012, 2:29:58 AM11/20/12
to std-pr...@isocpp.org


On Monday, November 19, 2012 11:04:28 PM UTC-8, viboes wrote:
Le 20/11/12 04:41, Nicol Bolas a écrit :


On Monday, November 19, 2012 6:59:15 PM UTC-8, Martin Desharnais wrote:
Alternativly, the alias declaration could be expand to support the explicit specifier:

#include <vector>

using A = int;
using explicit B = int;
template<typename T> using C = std::vector<T>;
template<typename T> using explicit D = std::vector<T>;

int main()
{
   
int n = 0;
    std
::vector<int> v = { 1, 2, 3 };

    A a
= n; // Implicit conversion
    B b
= n; // Error, no implicit conversion
    C
<int> c = v; // Implicit conversion
    D
<int> d = v; // Error, no implicit conversion
}

I think that would be consistent with explicit constructor and explicit conversion operator in that it disallow implicit conversion but stil let the programmer explicitly ask it.

I like the syntax here, particularly only allowing it with the new `using` syntax instead of the old `typedef`.

Yes unifying proposals would be great. Please, could you point me to the new using proposal?

It's not a proposal; that's standard C++11. You can use it for templates (which was the primary purpose), but it also can do everything that `typedef` could. And with much nicer syntax.
 
However, `enum class` already does something similar with enumerators. So we might want to use syntax like that instead of `explicit`:

using A = int;
using class B = int;
template<typename T> using C = std::vector<T>;
template<typename T> using class D = std::vector<T>;

Admittedly that's bikesheding. I do like the `class` being there, which helps show that you're not creating a type alias but a new type.
  
One of the major concerns of the Opaque proposals was to make the following difference:
Opaque typedef are explicitly convertible to the underlying type. But are they implicitly convertible? It depends. 
  • public opaque : implicit conversion to the underlying type
  • private opaque : explicit conversion to the underlying type
How the new syntax could take in account this point?
Does the new using proposal allows to add new function members?

It depends entirely on exactly what problems you're trying to solve. The one that Martin and I were solving was the ability to create an exact duplicate of a type, only with a different name. This is also the problem N1891 and N2141 are focused on solving.

Adding new function members is outside the scope of solving that problem.

Alisdair Meredith

unread,
Nov 20, 2012, 9:03:13 AM11/20/12
to std-pr...@isocpp.org
I'm not sure I understand why you think my suggestion of using inheriting constructors
prevents adding member functions.  I was simply highlighting that inheriting constructors
could emulate a solution if the user chose to use them in such a simple/restricted way.
Nothing prevents the addition of more function (or data!) members, and we do not need a
new syntax to support that.

Among the known reasons that inheriting constructors do not solve Walter's original
problem are that they cannot be used for the following types:
   fundamental types
   aggregate classes
   arrays
   unions
   final classes
   enumerations
   pointers
   pointer-to-members
   cv-qualified types
   function types
   incomplete types
   

There may well be some more type categories that I missed, but those are the ones
that I am immediately aware of.  We may not want such a feature to support *all* of
these cases, but this would be my primary motivation for adding a new language
feature to support Walter's original proposal - and I will not be writing that paper
myself ;~)

AlisdairM

--
 
 
 

Vicente J. Botet Escriba

unread,
Nov 20, 2012, 11:39:23 AM11/20/12
to std-pr...@isocpp.org
Le 20/11/12 15:03, Alisdair Meredith a écrit :
I'm not sure I understand why you think my suggestion of using inheriting constructors
prevents adding member functions.  I was simply highlighting that inheriting constructors
could emulate a solution if the user chose to use them in such a simple/restricted way.
Nothing prevents the addition of more function (or data!) members, and we do not need a
new syntax to support that.
I didn't want to intend this. Using a new class and inheriting the constructors allows of course to add members functions.



Among the known reasons that inheriting constructors do not solve Walter's original
problem are that they cannot be used for the following types:
   fundamental types
   aggregate classes
   arrays
   unions
   final classes
   enumerations
   pointers
   pointer-to-members
   cv-qualified types
   function types
   incomplete types
This is exactly how I understand it.

  

There may well be some more type categories that I missed, but those are the ones
that I am immediately aware of.  We may not want such a feature to support *all* of
these cases, but this would be my primary motivation for adding a new language
feature to support Walter's original proposal - and I will not be writing that paper
myself ;~)

My point is that  Walter's proposal could be extended to allow in addition member function.

Vicente

Vicente J. Botet Escriba

unread,
Nov 20, 2012, 12:06:13 PM11/20/12
to std-pr...@isocpp.org
Le 20/11/12 08:29, Nicol Bolas a écrit :


On Monday, November 19, 2012 11:04:28 PM UTC-8, viboes wrote:
Le 20/11/12 04:41, Nicol Bolas a écrit :


On Monday, November 19, 2012 6:59:15 PM UTC-8, Martin Desharnais wrote:
Alternativly, the alias declaration could be expand to support the explicit specifier:


I think that would be consistent with explicit constructor and explicit conversion operator in that it disallow implicit conversion but stil let the programmer explicitly ask it.

I like the syntax here, particularly only allowing it with the new `using` syntax instead of the old `typedef`.

Yes unifying proposals would be great. Please, could you point me to the new using proposal?

It's not a proposal; that's standard C++11. You can use it for templates (which was the primary purpose), but it also can do everything that `typedef` could. And with much nicer syntax.
I see. I thought alias were reserved to templates.

However, `enum class` already does something similar with enumerators. So we might want to use syntax like that instead of `explicit`:

using A = int;
using class B = int;
template<typename T> using C = std::vector<T>;
template<typename T> using class D = std::vector<T>;

Admittedly that's bikesheding. I do like the `class` being there, which helps show that you're not creating a type alias but a new type.
  
One of the major concerns of the Opaque proposals was to make the following difference:
Opaque typedef are explicitly convertible to the underlying type. But are they implicitly convertible? It depends. 
  • public opaque : implicit conversion to the underlying type
  • private opaque : explicit conversion to the underlying type
How the new syntax could take in account this point?
Does the new using proposal allows to add new function members?

It depends entirely on exactly what problems you're trying to solve. The one that Martin and I were solving was the ability to create an exact duplicate of a type, only with a different name. This is also the problem N1891 and N2141 are focused on solving.
Yes but how the alias syntax allows to make a difference between implicit/explicit conversions to the underlying type? Do you want to mean a different things with

using
A = int; // equivalent to a typedef, that is no new type
using class B = int; // a new type with explicit conversion to int?
using struct B = int; // a new type with implicit conversion to int?


Adding new function members is outside the scope of solving that problem.

Maybe. But IIRC this is something that has been discussed for enums.

Vicente

Martin Desharnais

unread,
Nov 20, 2012, 12:52:28 PM11/20/12
to std-pr...@isocpp.org

Yes but how the alias syntax allows to make a difference between implicit/explicit conversions to the underlying type? Do you want to mean a different things with

using
A = int; // equivalent to a typedef, that is no new type
using class B = int; // a new type with explicit conversion to int?
using struct B = int; // a new type with implicit conversion to int?

You are right that using "class/struct" specifier instead of "explicit" bring that question but, fortunatly, it have already been solved by "enum":


enum class A { a, b, c };
enum struct A { a, b, c }; // Error, multiple definition of ‘enum class A’

The same rule would apply with the extend alias directive.

--
Martin

Vicente J. Botet Escriba

unread,
Nov 20, 2012, 1:27:23 PM11/20/12
to std-pr...@isocpp.org
Le 20/11/12 18:52, Martin Desharnais a �crit�:

Yes but how the alias syntax allows to make a difference between implicit/explicit conversions to the underlying type? Do you want to mean a different things with

using
A = int; // equivalent to a typedef, that is no new type
using class B = int; // a new type with explicit conversion to int?
using struct B = int; // a new type with implicit conversion to int?

You are right that using "class/struct" specifier instead of "explicit" bring that question but, fortunatly, it have already been solved by "enum":


enum class A { a, b, c };
enum struct A { a, b, c }; // Error, multiple definition of �enum class A�

The same rule would apply with the extend alias directive.


enum class and enum struct are equivalent. So I think that using them for strongly types with a different semantics will not be coherent. I guess that we need to find out some keywords to mean EXPLICT or IMPLICIT conversion or something else.

using A = int; // equivalent to a typedef, that is, no new type
using B = EXPLICIT int; // a new type with explicit conversion to int?
using C = IMPLICIT int; // a new type with implicit conversion to int?


-- Vicente

grigor...@gmail.com

unread,
Nov 20, 2012, 1:33:58 PM11/20/12
to std-pr...@isocpp.org
     
using A = int; // equivalent to a typedef, that is, no new type
using B = EXPLICIT int; // a new type with explicit conversion to int?
using C = IMPLICIT int; // a new type with implicit conversion to int?


-- Vicente
 
 
I would suggest
 
  using A = int; // equivalent to a typedef, that is, no new type
 using new explicit B = int; // a new type with explicit conversion to int?

 using new C = int;          // a new type with implicit conversion to int?

 

 
Regards,
Gregory
 

Vicente J. Botet Escriba

unread,
Nov 20, 2012, 3:07:30 PM11/20/12
to std-pr...@isocpp.org
Le 20/11/12 19:33, grigor...@gmail.com a écrit :
+1
'new explicit' and 'new' convey quite well the intent. Where these keywords should be placed? After using as you are suggesting or

 using new B = explicit int; // a new type with explicit conversion to int?

 using new C = int;          // a new type with implicit conversion to int?

or

 using B = new explicit int; // a new type with explicit conversion to int?
 using C = new int;          // a new type with implicit conversion to int?

Vicente

grigor...@gmail.com

unread,
Nov 21, 2012, 4:22:40 AM11/21/12
to std-pr...@isocpp.org
I do not have strong preference for either option.

Вівторок, 20 листопада 2012 р. 22:07:34 UTC+2 користувач viboes написав:

DeadMG

unread,
Nov 21, 2012, 8:28:59 AM11/21/12
to std-pr...@isocpp.org, grigor...@gmail.com
using new B = explicit int;

I think this is the better choice. Having new T is just too confusing- people will start trying to declare variables that way.

Klaim - Joël Lamotte

unread,
Nov 21, 2012, 9:47:09 AM11/21/12
to std-pr...@isocpp.org, grigor...@gmail.com


On Wed, Nov 21, 2012 at 2:28 PM, DeadMG <wolfei...@gmail.com> wrote:
I think this is the better choice. Having new T is just too confusing- people will start trying to declare variables that way.

That being said, the word "new" is only used for runtime manipulations so far; it feels a bit confusing too (but without practice it's hard to say).
I prefer class for consistency, personally.

Whatever the naming, I felt the need for such feature very often too.

Joel Lamotte

grigor...@gmail.com

unread,
Nov 21, 2012, 10:05:29 AM11/21/12
to std-pr...@isocpp.org
By the way, it looks like a simple feature, but I have some reservations.
 
 
One of them is connected to fundamental types, ability to have new fundamental type unrelated to other fundamental types is a breaking change.
Other is about base classes. For example:
using new A = B;
What relation A has to base classes of B? What if we have virtual inheritance in the bases of B?
 
 Third is about explicit template specializations. We have explicit specialization for A, but none for B. Should we consider specialization for A as specialization for B?
 
Regards,
Gregory

Vicente J. Botet Escriba

unread,
Nov 21, 2012, 11:35:29 AM11/21/12
to std-pr...@isocpp.org
Le 21/11/12 16:05, grigor...@gmail.com a écrit :
By the way, it looks like a simple feature, but I have some reservations.
 
 
One of them is connected to fundamental types, ability to have new fundamental type unrelated to other fundamental types is a breaking change.
I don't see how. Could you elaborate?

Other is about base classes. For example:
using new A = B;
What relation A has to base classes of B? What if we have virtual inheritance in the bases of B?
I have not thought too much about that, but I guess that we can consider that A is a "meta-clone" of B, so if B has X as base class, A has also X as a base class. Note that A doesn't inherit from B. The single relation between A and B is convertibility/substitutability.
 
 Third is about explicit template specializations. We have explicit specialization for A, but none for B. Should we consider specialization for A as specialization for B?
I don't understand. Could you elaborate with  a concrete example?

Best,
Vicente


Vicente J. Botet Escriba

unread,
Nov 21, 2012, 4:59:40 PM11/21/12
to std-pr...@isocpp.org
Le 21/11/12 15:47, Klaim - Joël Lamotte a écrit :
If we follow the scoped scoped enum syntax and semantics, the following could be equivalent

  using class B = explicit int; // a new type with explicit conversion to int?
  using struct B = explicit int; // a new type with explicit conversion to int?

and the same for

  using class C = int;          // a new type with implicit conversion to int?
  using struct C = int;          // a new type with implicit conversion to int?


The alias-declaration grammar

alias-declaration:
  using identifier attribute-specifier-seqopt = type-id ;

could be replaced by

alias-declaration:
  alias-key identifier attribute-specifier-seqopt = explicitopt type-id ;

alias-key :
  using |
  using class |
  using struct

The optional explicit keyword before the type-id could appear only if the alias key is using class or using struct.

In addition to the 7 questions appearing in [1], I have some additional question.

Are the opaque types classes?

[1] says that the type traits of the OT are defined as the ones of the underlying type, is_TRAIT<OT> = is_TRAIT<UT>.
So if we have

  using class C = int;

is_class<C> should be false_type. This should be a little bit confusing, but there is an antecedent as an enum class is not a class.

I would prefer that opaque types be classes independently of the underlying types but maybe there are some issues with the core language.


Does the opaque type inherits from all the template classes specialized with the underlying type?

For example if we have a trait

template <typename T> struct aTrait;

specialized by the underlying type

template <> struct aTrait<UT>: true_type;

Is aTrait<OT> an opaque type for aTrait<UT>?

Which are the operations an opaque type provides?

An opaque type having a builtin type as underlying type should provide the same operations than the builtin type. It is less clear when the base type is a defined type.

I would say that the opaque type provides at least all the member functions defined by the underlying type or by its base classes. But what about the non-member functions that have as one of its parameters the underlying type? Providing all these functions will imply an implicit conversion of the opaque type to the underlying type. So if  the opaque type should provide a subset of these functions we need a mechanism to identify them. Now that there are more and more types that define its interface using non-members functions I would say that there is a need to 'inherit' from some of these functions.

I see two alternatives, either we identify them when defining the opaque type, or when defining the underlying type.

I'm for the last alternative and IMO, we need a language mechanism to identify the inherent operations of an underlying type which will be the ones provided by an associated opaque type.

The fist one will need to extend the alias declaration.

Should the user be able to define opaque types providing less functions than its underlying type?

I would say yes, but then the alias statement should be extended in some way.

Before analyzing more deeply these subjects in order to make a concrete proposal I would like to have some feedback.  Maybe all this is already clear for some of you and I'm missing something evident.


Best,

grigor...@gmail.com

unread,
Nov 21, 2012, 5:17:02 PM11/21/12
to std-pr...@isocpp.org

Here's an expanded version.

N1891 has quite detailed explanation of relations between aliased types. But it does not cover the relations to bases mentioned above.
 
And it states that

Given two template instantiations, one with OT [opaque type] as a template argument and the other with UT [underlying type] as the corresponding argument, how are the instantiations related?

Proposed answer: the two instantiations are unrelated.

Now, without opaque typedefs it was possible to write specializations for all floating point types or for all integer types, with opaque typedefs given that answer it would no longer be possible.

Moreover while N1891 states that all standard properties of type (exposed via type-traits) are the same for OT and UT, it also implies that OT does not keep non-standard properties of UT (like iterator traits), as these properties are defined via template specializations.

e.g. following is not possible

using new IntPtr = int*;

int arr[10] = {1,2,...};

std::accumulate((IntPtr)arr, (IntPtr)arr + 10);

It seems, that keeping non-standard properties is desirable. It is also desirable to have different specializations for UT and OT.

 

Regards,

Gregory

 

Nevin Liber

unread,
Nov 21, 2012, 5:40:03 PM11/21/12
to std-pr...@isocpp.org
On 21 November 2012 16:17, <grigor...@gmail.com> wrote:

Now, without opaque typedefs it was possible to write specializations for all floating point types or for all integer types, with opaque typedefs given that answer it would no longer be possible.

Presumably we can add another magic type trait to get to the UT from the OT, so that problem should be solvable.

I'm just much less clear on what is different and what is the same.

Given the following (syntax issues notwithstanding):

using A = unique_ptr<int>;
using B = explicit unique_ptr<int>;

what would the interfaces for A and B look like if they were written by hand?  Does the interface to unique_ptr<int> change?  What about free functions which take them (such as the unique_ptr comparison operators)?
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com(847) 691-1404

Klaim - Joël Lamotte

unread,
Nov 21, 2012, 6:09:53 PM11/21/12
to std-pr...@isocpp.org
Some feedback :

On Wed, Nov 21, 2012 at 10:59 PM, Vicente J. Botet Escriba <vicent...@wanadoo.fr> wrote:
Are the opaque types classes?

[1] says that the type traits of the OT are defined as the ones of the underlying type, is_TRAIT<OT> = is_TRAIT<UT>.
So if we have

  using class C = int;

is_class<C> should be false_type. This should be a little bit confusing, but there is an antecedent as an enum class is not a class.

I would prefer that opaque types be classes independently of the underlying types but maybe there are some issues with the core language.


I can understand that enum class cannot be considered the same way than a class.
However, here the feature is, as far as I understand it, supposed to "clone perfectly" the right-side type, which would imply that

is_class<C>::value == is_class<int>::value

Because otherwise I don't see the point of this feature.
 

Does the opaque type inherits from all the template classes specialized with the underlying type?


As I said, i don't see the point if the generated type isn't an exact clone.
 

Which are the operations an opaque type provides?


Same logic for me: it should just be a type clone.
 
Should the user be able to define opaque types providing less functions than its underlying type?

I would say yes, but then the alias statement should be extended in some way.

I don't see any use case for this. Do you have one? (I might miss something here).

Joel Lamotte


Vladimir Merzliakov

unread,
Nov 21, 2012, 6:16:17 PM11/21/12
to std-pr...@isocpp.org

Sorry if my comment will pointless and early discussed, but…

Why instead extending ‘using’ not allow just have non-class types as class parent types so have:

 

class C : private int { … }

 

or

 

class S : public int { … }

 

That directly allow easy define interface of new type and provide same implicit/explicit difference new type from original type

Using existed C++ syntax features.

 

Vladimir

 

 

From: Vicente J. Botet Escriba [mailto:vicent...@wanadoo.fr]
Sent: Thursday, November 22, 2012 2:00 AM
To: std-pr...@isocpp.org
Subject: Re: [std-proposals] Re: N1891: Progress toward Opaque Typedefs for C++0X

 

Should the user be able to define opaque types providing less functions than its underlying type?

I would say yes, but then the alias statement should be extended in some way.

Before analyzing more deeply these subjects in order to make a concrete proposal I would like to have some feedback.  Maybe all this is already clear for some of you and I'm missing something evident.


Best,
Vicente

[1] N1891: Progress toward Opaque Typedefs for C++0X

--
 
 
 

Klaim - Joël Lamotte

unread,
Nov 21, 2012, 6:19:12 PM11/21/12
to std-pr...@isocpp.org
Re-reading this discussion, I think I might have confused what I wish this feature would do with what was initially proposed.

My need would have been (as as said above) to make this:


   class K
   {
       int a;
       float b;
       string c;
       vector d;
    };

   using class U = K;

equivalent to 

   class K
   {
       int a;
       float b;
       string c;
       vector d;
    };

    class U
   {
       int a;
       float b;
       string c;
       vector d;
    }; 


Which would mean that the compiler just generate the type on the right side of the expression, 
as if it would have copy-pasted the declaration (and definition if available at this point) of the type and just changed the name.

Which would imply that:

is_[any test]< K > ::value == is_[any test]< U >

and

is_same< K, U > ::value == false


Which also imply that I don't see the point of the explicit optional marker.


Sorry if I'm being retarded about this, I might have misunderstood the original intent?

Joel Lamotte

Andrzej Krzemieński

unread,
Nov 22, 2012, 3:44:40 AM11/22/12
to std-pr...@isocpp.org

Which are the operations an opaque type provides?

An opaque type having a builtin type as underlying type should provide the same operations than the builtin type. It is less clear when the base type is a defined type.

I would say that the opaque type provides at least all the member functions defined by the underlying type or by its base classes. But what about the non-member functions that have as one of its parameters the underlying type? Providing all these functions will imply an implicit conversion of the opaque type to the underlying type. So if  the opaque type should provide a subset of these functions we need a mechanism to identify them. Now that there are more and more types that define its interface using non-members functions I would say that there is a need to 'inherit' from some of these functions.

I see two alternatives, either we identify them when defining the opaque type, or when defining the underlying type.

I'm for the last alternative and IMO, we need a language mechanism to identify the inherent operations of an underlying type which will be the ones provided by an associated opaque type.
 
My reply may be a bit off-topic, but you have touched a very interesting subject: type X's interface consists not only of its member functions but also of some free-standing functions. And clearly, not every free-standing function that takes X as argument is X's interface: only some of the functions. The need to identify this "extended interface" goes beyond opaque typedefs. For instance, concepts do this distinction. I believe (but I know the C++ Standard only from books like "D&E of C++") that not having this mechanism in place led to the introduction of ADL which would not be otherwise necessary.

grigor...@gmail.com

unread,
Nov 22, 2012, 4:58:22 AM11/22/12
to std-pr...@isocpp.org
I still have to write a proposal for named operators, but I believe that they solve that problem for new code.
 
Regards,
Gregory

Четвер, 22 листопада 2012 р. 10:44:41 UTC+2 користувач Andrzej Krzemieński написав:

Vicente J. Botet Escriba

unread,
Nov 29, 2012, 12:06:16 PM11/29/12
to std-pr...@isocpp.org
Le 22/11/12 10:58, grigor...@gmail.com a écrit :
I still have to write a proposal for named operators, but I believe that they solve that problem for new code.
 

Hi,

could you elaborate on how your future named operator proposal will address the mentioned problems?

Vicente
--
 
 
 

Vicente J. Botet Escriba

unread,
Nov 29, 2012, 12:26:24 PM11/29/12
to std-pr...@isocpp.org
Le 22/11/12 10:58, grigor...@gmail.com a écrit :
I still have to write a proposal for named operators, but I believe that they solve that problem for new code.
Hi,

I would share a possibility for the 2nd alternative, which consists in adding the prototype of all the associated functions just after the type definition

class X
{

}
{
  X& operator+(X const&, X const&);
  X& operator-(X const&, X const&);
}
;

The symbol '+'  could be used to separate the class members from the associated non-members functions.

Vicente

Vicente J. Botet Escriba

unread,
Nov 29, 2012, 12:54:38 PM11/29/12
to std-pr...@isocpp.org
Le 22/11/12 00:16, Vladimir Merzliakov a écrit :

Sorry if my comment will pointless and early discussed, but…

Why instead extending ‘using’ not allow just have non-class types as class parent types so have:

 

class C : private int { … }

Which would be the operations provided by C?

 

or

 

class S : public int { … }

This forces C to be a int and we want the new type C to be possibly not an int.

In addition the conversion from an int need to be added explicitly.

 

That directly allow easy define interface of new type and provide same implicit/explicit difference new type from original type

Using existed C++ syntax features.

Vicente

Vicente J. Botet Escriba

unread,
Nov 29, 2012, 1:00:00 PM11/29/12
to std-pr...@isocpp.org
Le 22/11/12 00:19, Klaim - Jo�l Lamotte a �crit :
> Re-reading this discussion, I think I might have confused what I wish
> this feature would do with what was initially proposed.
>
>
> ...
> Which also imply that I don't see the point of the explicit optional
> marker.
>
We want either that the conversion from the new type to the underlying
type to be implicit or explicit.

Vicente

Vicente J. Botet Escriba

unread,
Nov 29, 2012, 1:15:15 PM11/29/12
to std-pr...@isocpp.org
Le 22/11/12 00:09, Klaim - Joël Lamotte a écrit :
Some feedback :

On Wed, Nov 21, 2012 at 10:59 PM, Vicente J. Botet Escriba <vicent...@wanadoo.fr> wrote:
Are the opaque types classes?

[1] says that the type traits of the OT are defined as the ones of the underlying type, is_TRAIT<OT> = is_TRAIT<UT>.
So if we have

  using class C = int;

is_class<C> should be false_type. This should be a little bit confusing, but there is an antecedent as an enum class is not a class.

I would prefer that opaque types be classes independently of the underlying types but maybe there are some issues with the core language.


I can understand that enum class cannot be considered the same way than a class.
However, here the feature is, as far as I understand it, supposed to "clone perfectly" the right-side type, which would imply that

is_class<C>::value == is_class<int>::value

Because otherwise I don't see the point of this feature.
I agree as far as there is no mechanism to extend the opaque type with new operations.

 

Does the opaque type inherits from all the template classes specialized with the underlying type?


As I said, i don't see the point if the generated type isn't an exact clone.
Let me show an example. I can define a meta-function that tells if two classes belong to the same domain.

template <typename T, typename U>
struct same_domain : false_type {};

template <>
struct same_domain<A,B> : true_type{};

If I define a new opaque type

using class C = A;

should be same_domain<C,B> a true_type or false_type?


 

Which are the operations an opaque type provides?


Same logic for me: it should just be a type clone.
By clone I understand that it is a different type.

class A;
A& operator+(A const&,A const&);
using class C = A;

Could I expect the following to be valid

C c;
c = c + c ;

 
Should the user be able to define opaque types providing less functions than its underlying type?

I would say yes, but then the alias statement should be extended in some way.

I don't see any use case for this. Do you have one? (I might miss something here).

Think for example to a  process identifier new opaque type PId with an underlying type int. I wouldn't expect to be able to add two PId. But I expect them to be comparable, ....

Vicente

grigor...@gmail.com

unread,
Nov 30, 2012, 7:17:44 AM11/30/12
to std-pr...@isocpp.org

Четвер, 29 листопада 2012 р. 19:06:16 UTC+2 користувач viboes написав:
Le 22/11/12 10:58, grigor...@gmail.com a écrit :
I still have to write a proposal for named operators, but I believe that they solve that problem for new code.
 

Hi,

could you elaborate on how your future named operator proposal will address the mentioned problems?

Vicente
 
Named operator is a free function  declared with operator keyword.
 
A operator fun(const A& a);
 
Such functions should be considered as a part of an interface of its first (?) parameter.
That gives us a way to distinguish between usual free functions and free functions coupled to the class.
 
Type that is an opaque alias of some class should by default 'inherit' from it both usual and named operators.
That is, if we have
using class B = A;
 
And the above declaration of fun is visible then such declaration also defines  
 
B operator fun(const B& b);
 
Regards,
Gregory
 

Andrzej Krzemieński

unread,
Nov 30, 2012, 8:07:57 AM11/30/12
to std-pr...@isocpp.org, grigor...@gmail.com


W dniu piątek, 30 listopada 2012 13:17:44 UTC+1 użytkownik grigor...@gmail.com napisał:

Четвер, 29 листопада 2012 р. 19:06:16 UTC+2 користувач viboes написав:
Le 22/11/12 10:58, grigor...@gmail.com a écrit :
I still have to write a proposal for named operators, but I believe that they solve that problem for new code.
 

Hi,

could you elaborate on how your future named operator proposal will address the mentioned problems?

Vicente
 
Named operator is a free function  declared with operator keyword.
 
A operator fun(const A& a);
 
Such functions should be considered as a part of an interface of its first (?) parameter.

The constraint on only the first parameter is unacceptable (for me at least). The motivation for ADL was this:

class A{};
std
::ostream& operator<<(std::ostream&, A const&);

The operator is clearly A's interface, and A is second argument.

Regards,
&rzej

 

grigor...@gmail.com

unread,
Nov 30, 2012, 8:27:12 AM11/30/12
to std-pr...@isocpp.org, grigor...@gmail.com
That's why I have question mark there.
However, named operators and usual ones while having some similarities are different entities. I was talking specifically about named operators.
Maybe usual binary operators should be a part of interfaces of both their arguments, but for a named operator I would prefer to have a single class to which it should be connected.

Пʼятниця, 30 листопада 2012 р. 15:07:57 UTC+2 користувач Andrzej Krzemieński написав:

Vicente J. Botet Escriba

unread,
Nov 30, 2012, 1:25:40 PM11/30/12
to std-pr...@isocpp.org
Le 30/11/12 13:17, grigor...@gmail.com a écrit :

Четвер, 29 листопада 2012 р. 19:06:16 UTC+2 користувач viboes написав:
Le 22/11/12 10:58, grigor...@gmail.com a écrit :
I still have to write a proposal for named operators, but I believe that they solve that problem for new code.
 

Hi,

could you elaborate on how your future named operator proposal will address the mentioned problems?

Vicente
 
Named operator is a free function  declared with operator keyword.
 
A operator fun(const A& a);
 
Such functions should be considered as a part of an interface of its first (?) parameter.
That gives us a way to distinguish between usual free functions and free functions coupled to the class.
Are named operators a separated proposal? If yes, which is his added value? How the named operators are called?

I insists because I'm myself elaborating a proposal on named operators as an alternative to C++/CLI Properties that seams to have a completely different semantic.

Vicente

Klaim - Joël Lamotte

unread,
Nov 30, 2012, 4:08:47 PM11/30/12
to std-pr...@isocpp.org
On Thu, Nov 29, 2012 at 7:15 PM, Vicente J. Botet Escriba <vicent...@wanadoo.fr> wrote:
I agree as far as there is no mechanism to extend the opaque type with new operations.


You mean associated non-member functions? 
Let me show an example. I can define a meta-function that tells if two classes belong to the same domain.

template <typename T, typename U>
struct same_domain : false_type {};

template <>
struct same_domain<A,B> : true_type{};

If I define a new opaque type

using class C = A;

should be same_domain<C,B> a true_type or false_type?



I would say false_type but I'm not sure if I'm missing a unfortunate side effect in this specific case.
 
 

Which are the operations an opaque type provides?


Same logic for me: it should just be a type clone.
By clone I understand that it is a different type.

class A;
A& operator+(A const&,A const&);
using class C = A;

Could I expect the following to be valid

C c;
c = c + c ;



Ah yes indeed, I understand the problem now. Thanks!
Hard to solve...

Joel Lamotte

grigor...@gmail.com

unread,
Dec 1, 2012, 8:32:02 AM12/1/12
to std-pr...@isocpp.org
Named operators allow strict definition of extended interface of a class. That notion is useful not only for opaque aliases. I started thinking about it when I read ADL Control for C++.
There was proposed to have an option to disable ADL for all non-operator names in the current scope except for those that are mentioned in a special using directive. In my opinion it was okay to disable ADL for most of the functions, however not for those that are connected to a class, and those may be usual functions not only operators. 
So, my initial idea was to have named operators proposal as addendum to the N3490.
 
If we stop here, then named operators are just specially declared free functions and are called as such:
func(a,b);
or
operator func(a,b);
 
But there was another idea - to open-up classes in C++, that is to allow a limited form of partial classes of C#/VB - allow additional definitions of class that contain typedefs and members. It seems with using aliases we on a longer term will not need inner typedefs anyway, but adding new non-virtual functions to exiting class may be beneficial - with that we would have a sort of concept maps for non-generic code.
 
That is possible if we allow named operators to be called like members. That is the second part of the proposal. There are unsolved problems however, it is tempting to have an ability to overload existing member-function with named operator, but member-function if it matches parameters should always be considered as better match than named operator, that leads to more complicated matching procedure...
Anyway, with second part of the proposal named operators may be called as member functions:
a.func(b);
 
Regards,
Gregory

Пʼятниця, 30 листопада 2012 р. 20:25:40 UTC+2 користувач viboes написав:

Vicente J. Botet Escriba

unread,
Dec 8, 2012, 8:29:50 AM12/8/12
to std-pr...@isocpp.org
Le 01/12/12 14:32, grigor...@gmail.com a écrit :
Named operators allow strict definition of extended interface of a class. That notion is useful not only for opaque aliases. I started thinking about it when I read ADL Control for C++.
There was proposed to have an option to disable ADL for all non-operator names in the current scope except for those that are mentioned in a special using directive. In my opinion it was okay to disable ADL for most of the functions, however not for those that are connected to a class, and those may be usual functions not only operators. 
So, my initial idea was to have named operators proposal as addendum to the N3490.
I don't understand the rationale of why is needed to make a difference for operators in N3490.

 
If we stop here, then named operators are just specially declared free functions and are called as such:
func(a,b);
or
operator func(a,b);
 
But there was another idea - to open-up classes in C++, that is to allow a limited form of partial classes of C#/VB - allow additional definitions of class that contain typedefs and members. It seems with using aliases we on a longer term will not need inner typedefs anyway, but adding new non-virtual functions to exiting class may be beneficial - with that we would have a sort of concept maps for non-generic code.
 
That is possible if we allow named operators to be called like members. That is the second part of the proposal. There are unsolved problems however, it is tempting to have an ability to overload existing member-function with named operator, but member-function if it matches parameters should always be considered as better match than named operator, that leads to more complicated matching procedure...
Anyway, with second part of the proposal named operators may be called as member functions:
a.func(b);
  
IIUC your idea, the trouble I see with your named operators applied to strong types is that the inherited interface is open and depends on which files are you seen. The same applies to the open-up classes proposal. I really think that we nedd a single point on which non-member functions are added to the set of inherited functions.

Best,
Vicente

grigor...@gmail.com

unread,
Dec 9, 2012, 7:17:14 AM12/9/12
to std-pr...@isocpp.org


Субота, 8 грудня 2012 р. 15:29:50 UTC+2 користувач viboes написав:
I don't understand the rationale of why is needed to make a difference for operators in N3490.

The main reason for introducing ADL was to keep the expected behavior for operators in the presence of namespaces. N3490 tries to get ADL right, that is to turn it off for those functions that are never inteded to be found by ADL, but keep it for functions that should be found by ADL (operators). 
   
IIUC your idea, the trouble I see with your named operators applied to strong types is that the inherited interface is open and depends on which files are you seen. The same applies to the open-up classes proposal. I really think that we nedd a single point on which non-member functions are added to the set of inherited functions.

Why do you consider this as a trouble? Opening-up classes, that is allowing to add new member-functions to existing classes in non-intrusive way was the whole point of N1742. 

Regards,
Gregory

Vicente J. Botet Escriba

unread,
Dec 9, 2012, 8:48:27 AM12/9/12
to std-pr...@isocpp.org
Le 09/12/12 13:17, grigor...@gmail.com a écritО©Ґ:


О©ҐО©ҐО©ҐО©ҐО©ҐО©Ґ, 8 О©ҐО©ҐО©ҐО©ҐО©ҐО©Ґ 2012 О©Ґ. 15:29:50 UTC+2 О©ҐО©ҐО©ҐО©ҐО©ҐО©ҐО©ҐО©ҐО©ҐО©Ґ viboes О©ҐО©ҐО©ҐО©ҐО©ҐО©ҐО©Ґ:
I don't understand the rationale of why is needed to make a difference for operators in N3490.

The main reason for introducing ADL was to keep the expected behavior for operators in the presence of namespaces. N3490 tries to get ADL right, that is to turn it off for those functions that are never inteded to be found by ADL, but keep it for functions that should be found by ADL (operators).О©Ґ
О©ҐО©Ґ
I believe I understand it now.
IIUC your idea, the trouble I see with your named operators applied to strong types is that the inherited interface is open and depends on which files are you seen. The same applies to the open-up classes proposal. I really think that we nedd a single point on which non-member functions are added to the set of inherited functions.

Why do you consider this as a trouble? Opening-up classes, that is allowing to add new member-functions to existing classes in non-intrusive way was the whole point of N1742.О©Ґ

I understand the point of N1742. What I mean is that I'm not sure all the new member-functions added to the underlying type (using N1742) should be inherited by the strong type. Or are you suggesting that the user should use the N1742 mechanism to extend the operations on the Strong type?

In addition, the N1742 mechanism doesn't covers all the operations a strongly type could inherit from, as it forces to change the call syntax of the inherited function.

Best,
Vicente

grigor...@gmail.com

unread,
Dec 9, 2012, 10:31:53 AM12/9/12
to std-pr...@isocpp.org


I understand the point of N1742. What I mean is that I'm not sure all the new member-functions added to the underlying type (using N1742) should be inherited by the strong type. Or are you suggesting that the user should use the N1742 mechanism to extend the operations on the Strong type?

In addition, the N1742 mechanism doesn't covers all the operations a strongly type could inherit from, as it forces to change the call syntax of the inherited function.


I do not propose mechanism used in N1742. I'm saying that in my view classes should be more open than they are now in C++. And the existence of N1742 suggests that I'm not alone :).  
However, whatever mechanism we would have for extending existing classes there's no reason not to apply this mechanism to opaque aliases. 
If opaque aliases inherits all operations of aliased type, then it should inherit those additional functions as well. 

I understand your reservations though. If there's need to provide restricted interface for alias compared to the interface of original type, such implicit inheritance of all operations do not help. 
IMHO if there's real need to have restricted interfaces for opaque aliases then it should be addressed separately. 

Regards,
Gregory

Andrzej Krzemieński

unread,
Dec 12, 2012, 12:07:40 PM12/12/12
to std-pr...@isocpp.org, grigor...@gmail.com


W dniu niedziela, 9 grudnia 2012 13:17:14 UTC+1 użytkownik grigor...@gmail.com napisał:


Субота, 8 грудня 2012 р. 15:29:50 UTC+2 користувач viboes написав:
I don't understand the rationale of why is needed to make a difference for operators in N3490.

The main reason for introducing ADL was to keep the expected behavior for operators in the presence of namespaces. N3490 tries to get ADL right, that is to turn it off for those functions that are never inteded to be found by ADL, but keep it for functions that should be found by ADL (operators). 

Just one clarification here. Perhaps a bit off topic. While operators might have been the primary motivation for ADL, non-operator functions also now depend on it. The example I have in mind is swap(), which in a way has something of an operator.

Regards,
&rzej

Nevin Liber

unread,
Dec 12, 2012, 12:52:39 PM12/12/12
to std-pr...@isocpp.org
On 12 December 2012 11:07, Andrzej Krzemieński <akrz...@gmail.com> wrote:

Just one clarification here. Perhaps a bit off topic. While operators might have been the primary motivation for ADL, non-operator functions also now depend on it. The example I have in mind is swap(), which in a way has something of an operator.

Also begin and end for range-based for.

3dw...@verizon.net

unread,
Mar 19, 2013, 2:32:00 PM3/19/13
to std-pr...@isocpp.org


On Monday, November 19, 2012 8:16:12 PM UTC-5, Vicente J. Botet Escriba wrote:
Hi,

there is an old proposal [1] from Walter E. Brown about opaque types that I have found quite interesting.

One of the motivating examples is the ability to overload on these new types


typedef public  double X, Y, Z; // Cartesian 3D coordinate types
typedef public double Rho, Theta, Phi; // polar 3D coordinate types
class PhysicsVector
{
public:
  PhysicsVector(X, Y, Z);
  PhysicsVector(Rho, Theta, Phi);
...
}; // PhysicsVector

Could some one tell me if the committee was interested on this proposal? Is there a plant to resurrect it?

Alisdair Meredith showed in [2] that forward constructor could help on this but that there are some issues that are not addressed by this construction.

struct MyType : std::string {
  using string::string;
};

I'm wondering if the syntax couldn't be adapted so that the new type could contain more function members.

class Address using public std::string {
public:
  
};
Note that there is no need to add forward constructors as the using keyword would intend already to have the same interface as the underlying type. 

If Address should not be seen implicitly as a std::string the using should be private

class Address using private std::string {
  // possibility to add new member functions
};

Note that builtin types could also be used as underlying types as it is the case of the Opaque typedef proposal.

class Temperature using double {
  // possibility to add new member functions
};

By default the new type has the same operations than the underlying type. Restricting the interface of the underlying type should also be possible if the operation is decorated with delete.

struct Address using std::string {
  std::size_t length() const = delete;
};

One question is whether the new class can add new data members. I don't think tis is a good idea the new type and the underlying type should have the same size.

An alternative could be to use a new keyword (e.g. alias) so that this constraint is clearer.

alias Address using std::string {
  std::size_t length() const = delete;
};

Is there some interest?


I have long wondered if allowing classes to derive from basic types would be helpful:

class Energy : public double
{
 
// possibly override operators, add user-defined literals, etc.
 
// Otherwise have all arithmetic operators and I/O because of public derivation from double.
};

The class Energy would not be implicitly convertible to double.  It might get by default an explicit operator to double, I'm not sure.  To my eye this syntax is easy to learn because it mimics regular class definition.  OTOH, I'm not sure one should be allowed to add members and what would happen if you did.  I guess all the inherited operators would only touch the 'double' part - similar to what happens in class hierarchies today.

Lawrence Crowl

unread,
Mar 20, 2013, 4:50:16 PM3/20/13
to std-pr...@isocpp.org
On 3/19/13, 3dw...@verizon.net <3dw...@verizon.net> wrote:
> I have long wondered if allowing classes to derive from basic
> types would be helpful:
>
> class Energy : public double
> {
> // possibly override operators, add user-defined literals, etc.
> // Otherwise have all arithmetic operators and I/O because of
> // public derivation from double.
> };
>
> The class Energy would not be implicitly convertible to double.
> It might get by default an explicit operator to double, I'm
> not sure. To my eye this syntax is easy to learn because it
> mimics regular class definition. OTOH, I'm not sure one should be
> allowed to add members and what would happen if you did. I guess
> all the inherited operators would only touch the 'double' part -
> similar to what happens in class hierarchies today.

The normal semantics of public inheritance would make conversion
from Energy& to double& pretty strongly available. I don't think
we should change that.

For private inheritance, we would need to explicitly reintroduce
the operators, perhaps with =default. I don't think this extra
work is necessarily a bad thing, because Energy*Energy is not Energy.

If the intent is to create 'units', then I think we are doomed to
fail with this approach. We need to track value/representation
separately from the units.

--
Lawrence Crowl

Tony V E

unread,
Mar 20, 2013, 4:55:06 PM3/20/13
to std-pr...@isocpp.org
On Wed, Mar 20, 2013 at 4:50 PM, Lawrence Crowl <cr...@googlers.com> wrote:
>
> The normal semantics of public inheritance would make conversion
> from Energy& to double& pretty strongly available. I don't think
> we should change that.

definitely agreed. I was about to say the same thing.

>
> For private inheritance, we would need to explicitly reintroduce
> the operators, perhaps with =default.

wouldn't it be via 'using', like you do normally for pulling base
functions into derived space?
I guess the syntax might be:

using double::operator*;

> I don't think this extra
> work is necessarily a bad thing, because Energy*Energy is not Energy.
>
> If the intent is to create 'units', then I think we are doomed to
> fail with this approach. We need to track value/representation
> separately from the units.
>
> --
> Lawrence Crowl
>
> --
>

Tony

Nevin Liber

unread,
Mar 20, 2013, 5:52:07 PM3/20/13
to std-pr...@isocpp.org
On 20 March 2013 15:55, Tony V E <tvan...@gmail.com> wrote:
> For private inheritance, we would need to explicitly reintroduce
> the operators, perhaps with =default.

wouldn't it be via 'using', like you do normally for pulling base
functions into derived space?
I guess the syntax might be:

using double::operator*;

Wouldn't that be for multiplying Energy with doubles, not multiplying Energy with Energy?
 
> I don't think this extra
> work is necessarily a bad thing, because Energy*Energy is not Energy.

It depends.  The problem is, the tedious extra work (such as for '+', where Energy+Energy is Energy) keeps people from writing little types and instead just falling back on using already existing "rich" types (fundamental types, std::string, etc.) everywhere.

Tony V E

unread,
Mar 20, 2013, 6:01:05 PM3/20/13
to std-pr...@isocpp.org
On Wed, Mar 20, 2013 at 5:52 PM, Nevin Liber <ne...@eviloverlord.com> wrote:
> On 20 March 2013 15:55, Tony V E <tvan...@gmail.com> wrote:
>>
>> > For private inheritance, we would need to explicitly reintroduce
>> > the operators, perhaps with =default.
>>
>> wouldn't it be via 'using', like you do normally for pulling base
>> functions into derived space?
>> I guess the syntax might be:
>>
>> using double::operator*;
>
>
> Wouldn't that be for multiplying Energy with doubles, not multiplying Energy
> with Energy?
>

right. Good point.
Reply all
Reply to author
Forward
0 new messages