This is a C++ library I created to be able to wrap types while specifying - in compile-time - what operations, if any, are to be preserved, i.e., what homomophisms should be injected into the wrapping type. One can view it as a very flexible type alias system.
I issued this library as an informal proposal to Boost. The creation of this library was a reaction to an "alias" proposal by Beman Daves called identifier to that same community, so I reasoned that it was the proper audience for my, more generic, library.
I simply thought that there were more to be said about "similar" types, e.g., that:
- there could be other operations but assignment and comparison that one might want to preserve from the original type
- the actual operations to be preserved should be gathered in policies
- such policies should be injected during compile-time
- operations from original type should not only be preservable but alterable
The response from the Boost community was massive silence. That does not take away the feeling that my approach is The Right One, and that the identity is but a special, and easily defined, case of my library.
So, let us see what embed_type can do for us.
Before that, actually, we have to understand why we cannot use the alias construct of C++, typedef. This construct introduces names that are undistinguishable from the original type, i.e., a simple syntactic shortcut. The big problem in this is that one cannot overload operators and functions specifically for that introduced alias. I.e., the two function declarations below will result in duplicate declaration attempts (and will be silently ignored):
-
typedef int CustomerId;
-
void addCustomer(CustomerId id);
-
void addCustomer(int num);
or, perhaps more crucially, the following snippet will fail to compile. One cannot overload + for integers:
-
typedef int CustomerId;
-
CustomerId operator+(CustomerId custId2, CustomerId custId2);
Ok, the reason for "adding" two customer IDs might not be obvious at first (or fifth...) sight, and even if so, the reason to add them differently than what is done for the underlying integers is even less obvious. That said, I hope you get my point.
Perhaps you should think about the attempt to use ++myCustId as a way to get the next available ID, which might differ from the next consecutive integer.
For you Haskell developers out there, I wanted to create an adaptive newtype for C++, instead of type.
Now for a first real use case, where we create a construct that is more or less equivalent to Daves' identity proposal, i.e., a simple alias:
-
template<typename OrigT, typename AliasT>
-
struct identifier :
-
embed_type<OrigT, AliasT, mpl::vector<
-
policy<embed_order>, policy<embed_equivalence>>>
-
{
-
identifier(OrigT orig) : embed_type(orig) {}
-
};
What we said here is that the ordering operations and equivalence should be preserved by our mapping. Since the mapping, identifier is generic itself, it can subsequently be used in cases where we want an alias to a type, such as
-
struct CustomerId : identifier<int, CustomerId> {
-
CustomerId(int n) : identifier(n) {}
-
};
These CustomerId can then be used as int w.r.t. ordering and equivalence. If you want an integral type that also supports arithmetic, you should do
-
struct AlmostInt : embed_type<int,
-
mpl::vector<policy<embed_order>, policy<embed_equivalence>,
-
policy<embed_arithmetics>> {
-
AlmostInt(int n) : embed_type(n) {}
-
};
Here we used three policies, to preserve most operations from int. Thus, we can now do
-
AlmostInt m = 42;
-
AlmostInt n = m + 1;
The second line will automatically convert 1 to an AlmostInt and then perform the addition. What we cannot do is
-
AlmostInt m = 42;
-
int n = m;
Since conversion to int is not injected into the new type. Adding that is simple: just add policy to the vector of types provided to the embed_type template.
Ok, where is all the magic?
Well, first of all, my library uses Boost.MPL heavily, such as to hold vector of types. Secondly, I created a handful of generic policies, such as that embed_policy. Thirdly, I rely on my proposal gen_hierarchy to handle the injection of base types into the wrapper type. A discussion about that library is found in an earlier post.
My policies are C++ templates, and Boost.MPL expects special classes - they call them meta function classes - which explains the use of the policy identifier.
I supply both a test program and the library here. Will talk more about embed_type later.
Enjoy!
/* File: test_embed_type.hpp Author: David Bergman Date: 2006-07-25 This is a simple test of the 'embed_type' proposal, which is actually just a slightly decoupled (policy-wise) and more generic version of Beman Daves' "identifier" proposal. */ #include <iostream> #include <boost/mpl/vector.hpp> #include <boost/mpl/remove.hpp> #include <boost/embed_type.hpp> // We include Beman Daves' identifier here as a special case. // Here, only the order and equivalence are preserved from the raw type. namespace boost { template<typename T, typename D> class identifier : public embed_type<T, D, mpl::vector<policy<embed_order>, policy<embed_equivalence> > > {}; } // namespace boost // Create a customer Id type which is *almost* isomorphic to int, // except for conversion to int typedef boost::mpl::remove<boost::embed_policy_all, boost::policy<boost::embed_conversion> >::type policy_no_convert; class CustomerId : public boost::embed_type<int, CustomerId, policy_no_convert> { public: CustomerId() {} // Ugh, GCC does not support mentioning only the template // name for sub classes :-( CustomerId(int num) : boost::embed_type<int, CustomerId, policy_no_convert>(num) {} }; int main(int argc, const char* argv[]) { CustomerId id = 42; id += 2; // works thanks to the inclusion of embed_arithmetics int idNum = static_cast<int>(id); // only works if you add the embed_conversion polocy std::cout << "Id is CustomerId(" << idNum << ")" << std::endl; }
Download this code: test_embed_type.cpp
#ifndef EMBED_TYPE_HPP #define EMBED_TYPE_HPP #include <boost/mpl/transform.hpp> #include <boost/mpl/placeholders.hpp> #include <boost/generate_hierarchy.hpp> // my sibling proposal namespace boost { /* File: embed_type.hpp Author: David Bergman, based on Beman Daves' 'identifier' proposal. Date: 2006-07-25 These templates and classes embed a (primitive or not) type, and with a set of policies decides what aspects of that type should be exposed in the embedding. */ /* The various policies that we use in the final embedding. The policies are either implemented or not, depending on a template flag. They constitute an inheritance chain of types. We use the proposed generate_chain meta construct to generate the actual chain of policies. So, all policies are MPL meta function classes generating MPL meta function classes, i.e., meta meta function classes. The first application is to get the particular policy node (with a hole) from the two types and the second application is to get the hierarchy up to that point. The template parameter D signifies the embedding type. In order to create your own embedding policies you "just" have to to create a meta meta function class or - easier - a teriary template and use the utility 'to_mmf'. */ // We first need a utility that creates the MPL meta meta function class // - or curried meta function class - // from a plain template taking three parameters. template <template <typename T, typename D, typename Base> class Policy> struct policy : quote_raw3<Policy> {}; // // Now the various policies already implemented // // Order template <typename T, typename D, typename C> struct embed_order : C { bool operator<(const D& rhs) const { return this->value_ref() < rhs.value_ref(); } bool operator<=(const D& rhs) const { return this->value_ref() <= rhs.value_ref(); } bool operator>(const D& rhs) const { return this->value_ref() > rhs.value_ref(); } bool operator>=(const D& rhs) const { return this->value_ref() >= rhs.value_ref(); } }; // Equivalence template <typename T, typename D, typename C> struct embed_equivalence : C { bool operator==(const D& rhs) const { return this->value_ref() == rhs.value_ref(); } bool operator!=(const D& rhs) const { return this->value_ref() != rhs.value_ref(); } }; // Assignability from source type template <typename T, typename D, typename C> struct embed_assignability : C { D& operator=(const T& t) { this->value_ref(t); return *this; } }; // Conversion to source template <typename T, typename D, typename C> struct embed_conversion : C { operator T() const { return this->value_ref(); } }; // Arithmetics (+, -, +=, -= at least...) template <typename T, typename D, typename C> struct embed_arithmetics : C { D operator+(const D& rhs) const { return D(this->value_ref() + rhs.value_ref()); } D operator-(const D& rhs) const { return D(this->value_ref() - rhs.value_ref()); } D& operator+=(const D& rhs) { this->value_ref() += rhs.value_ref(); return static_cast<D&>(*this); } D& operator-=(const D& rhs) { this->value_ref() -= rhs.value_ref(); return static_cast<D&>(*this); } }; /* The structure-holding type */ template <typename T> class embed_structure { protected: embed_structure() {}; embed_structure(const T& m_value) : m_value(m_value) {} public: T value() const { return m_value; } protected: void value_ref(const T& value) { m_value = value; } T& value_ref() { return m_value; } const T& value_ref() const { return m_value; } private: T m_value; }; /* The (conglomerate) embedding itself, which expects a policy sequence. If no such policy list is provided, "all" structures are preserved (and embedded) */ namespace { // TODO: This helper is ugly, please use MPL functions instead // to achieve this simple flip of parameters! template <typename T, typename D> struct create_policy_node { template <typename Policy> struct apply { typedef mpl::bind<Policy, T, D, mpl::_1> type; }; }; // Map a sequence of policy meta function classes expecting // T and D to meta function classes ready to be chained together template <typename PolicySeq, typename T, typename D> struct create_policy_nodes : mpl::transform<PolicySeq, create_policy_node<T, D> >::type {}; } // namespace anonymous // All policies enabled, i.e., creating an isomorphism typedef mpl::vector<policy<embed_order>, policy<embed_equivalence>, policy<embed_conversion>, policy<embed_assignability>, policy<embed_arithmetics> > embed_policy_all; template<typename T, typename D, typename PolicySeq = embed_policy_all> class embed_type : // We just have to apply the given policy meta meta functions public generate_chain<create_policy_nodes<PolicySeq, T, D>, embed_structure<T> > { protected: embed_type() {} embed_type(const T& value) { this->value_ref(value); }; }; } // namespace boost #endif
Download this code: embed_type.hpp
boost C++ embed embed type mpl proxy templates type type alias wrapper