
Beman wrote:
While integers are often used as the underlying type, any type that meets a few common requirements (CopyConstructible, etc.) can be used. One of my test cases uses std::string.
Yes, but if I wanted to embed the type std::string with my specialization, for some special handling (perhaps to create a Pascal string as its conversion to "char*"? ;-) ), the term (and idea of) 'identifier' does not make much sense. I.e., I felt your proposal - with some modifications and extensions - to be applicable in a far wider scope.
I'm on a road-trip so may not get a chance to look at your stuff for a few days. One thing I'll look at then is to compare several common uses of identifier against your proposal to see if the added generality gets in the way of simple use cases.
The identifier notion could be implemented as typedef mpl::vector< policy<embed_order>, // propagate order policy<embed_equivalence>, // propagate equivalence policy<embed_assignability> // propagate assign-from-raw > ident_policies; template <class T, class D> struct identifier : embed_type<T, D, ident_policies> { identifier(T t) : embed_type<T, D, ident_policies>(t) {}; }; after which 'identifier' should carry your semantics, except for stream operators which are not currently implemented in the embed_type proposal and your clever use of 'unspecified_bool_...'. Ah, I see that you have a boolean negation operator. That could be a useful preservation, so let's define a policy for it: template <T, D, Base> struct embed_neg : Base { bool operator!() const { return !this->value_ref(); } }; and then add it, as template <T, D> struct identifier2 : embed_type<T, D, mpl::push_back<ident_policies, policy<embed_neg> >::type> { identifier2(T t) : embed_type<....>(t) {} }; One could also possibly use SFINAE to bypass policies (read "homomorphisms") not making any sense for the specific embedded type, such as trying to add policy<embed_arithmetics> to struct Foo { void foo(); }; I will investigate that extension.
The other question is how important/useful are the other possible uses of your proposal that go beyond identifier.
I see a lot of proxy/wrapper uses, where most aspects/structures are preserved, but some are not. The policy system also allows for concept-specific homomorphisms to be implemented, building up a portfolio, and then used in concrete cases. An example of a float wrapper where we do NOT want to propagate arithmetics: struct almost_float : embed_type<float, almost_float, mpl::remove<embed_policy_all, policy<embed_arithmetics> > > { almost_float(float f) : embed_type<....>(t) {} }; One could also not only chose not to propagate a behavior but to replace it, such this policy restricting arithmetics to positive values, i.e., enforcing a non-group (algebraically) behavior: template<class T, class D, class Base> struct pos_arith_policy : Base { operator D operator+(const D& rhs) const { return D(this->value_ref() + rhs.value_ref()); } operator D operator-(const D& rhs) const { if (this->value_ref() < rhs.value_ref()) throw std::runtime_error("Oops, we are not in a group anymore, so be careful!"); return D(this->value_ref() - rhs.value_ref()); } }; which can be used to define a special policy list replacing ordinary arithmetics with the checked variant: typedef mpl::vector< mpl::push_back< mpl::remove<embed_policy_all, policy<embed_arithmetics> >, policy<pos_arith_policy> > positive_arithmetics_policies; With this policy list, we can create positive variants of any arithmetic type, such as: struct positive_float : embed_type<float, positive_float, positive_arithmetics_policies> { positive_float(float f) : embed_type<....>(f) { // Make sure we start out with a non-negative float as well... if (f < 0) throw std::runtime_error("If you want positive, please do not start out negative!"); } }; We can now use this positive_float as any float, but with the added positive restriction: positive_float myFloat1(41.0); float temp = myFloat1; // fine, since we inject embed_conversion positive_float myFloat2 = myFloat1 + 1.0; // fine positive_float myFloat3 = myFloat2 - 43.0; // Oops, will trow! One cannot only extend a type with checks, such as these, but also distort behavior, such as with this flipped arithmetics policy, of whose usability one can discuss; perhaps in a stack implementation of some form, or for transparent implementation of a reverse iterator?: template<class T, class D, class Base> struct flip_arith_policy : Base { D operator+(const D& rhs) const { return D(this->value_ref() - this->value_ref()); } D operator-(const D& rhs) const { return D(this->value_ref() + this->value_ref()); } }; Let's use it: struct flipped_int : embed_type<int, flipped_int, mpl::vector<policy<embed_conversion>, policy<flip_arith_policy> > > { flipped_int(int n) : embed_type<...>(n) {} }; flipped_int myInt1 = 43; flipped_int myInt2 = myInt + 1; std::cout << "43 + 1 = " << myInt2 << std::endl; Yes, you guessed it: 42! I hope this triggered some use cases, even though, admittedly, my examples here are pretty lame. /David