I was thinking further about my idea, and realized the problem with this. It works well for basic purposes: to enforce certain functions to have a set of features. However, it doesn't work for automatic overload resolution. For example, if we have a function with an overload that is thread safe, and another that is both thread safe and exception safe, and we call it with a requirement of being only thread safe, we will get an error of ambiguous overload.
The original solution uses virtual inheritance, which provides a built-in overload resolution choosing the closest possible match. With implicit conversion, we can't use this algorithm. We have to choose the overload "manually" via
enable_if
. I built a forwarding of
enable_if
that computes this without the need to write large expressions for every overload. The disadvantage is that we need to know all the overloads in advance. Here is how it works.
namespace mpl = boost::mpl;
namespace cf = CodeFeatures;
typedef cf::Features<mpl::vector<ThreadSafe>> TSafe;
typedef cf::Features<mpl::vector<ThreadSafe, ExceptionSafe>> TESafe;
typedef mpl::vector<TSafe, TESafe> CalleeFeaturesList;
template <class Features>
typename cf::SelectFeatures<Features, TSafe, CalleeFeaturesList>::type
callee()
{
std::cout << "TSafe" << std::endl;
}
template <class Features>
typename cf::SelectFeatures<Features, TESafe, CalleeFeaturesList>::type
callee()
{
std::cout << "TESafe" << std::endl;
}
This version has advantages over the original implementation as well. First, as it uses templates and decides which overload to use with the metafunction on the top, we don't even need to instantiate the feature variables. Second, the template makes these functions pass its requirement transitively. For example, we have a caller function like this:
template <class Features>
typename cf::SelectFeatures<Features, TESafe, mpl::vector<TESafe>>::type
caller()
{
callee<Features>();
}
This function provides both thread safety and exception safety to its callers, and has no overloads. However, if we only need thread safety, it will call the TSafe version of
callee
, providing a more efficient program without needing to use overloads over the whole call hierarchy.
Of course, if we don't want to use templates, the original approach works just as fine:
typedef TESafe callerFeatures;
void callerNoTemplate(callerFeatures features)
{
calleeNoTemplate(features); // the original way
callee<callerFeatures>(); // of course we can't use the variable in the template parameter. We have to use the type name itself.
}
Here is the implementation. First, I introduced some new metafunctions because the lack of
contains_if
in Boost MPL.
template <typename Seq, typename Pred>
struct none_of: boost::is_same<
typename boost::mpl::find_if<Seq, Pred>::type,
typename boost::mpl::end<Seq>::type
> {};
template <typename Seq, typename Pred>
struct all_of: none_of<
Seq,
boost::mpl::not_<Pred>
> {};
With this, even the definition of
Features
becomes easier. Otherwise, it does the same as before (except that in my last post, I left out the default constructor).
template <typename S>
struct Features
{
typedef S features_type;
Features() {}
template <typename OtherFeature>
Features(
const OtherFeature&,
typename boost::enable_if<
typename detail::all_of<
typename OtherFeature::features_type,
boost::mpl::contains<S, _>
>::type,
int
>::type = 0)
{}
};
Now comes the implementation for
SelectFeatures
. In human words, it does the following: "Enable this overload if
T
is convertible to
ThisType
and not convertible to any types in
AllTypes
which
ThisType
is also not convertible to."
template <typename T, typename ThisType, typename AllTypes, typename ValueType = void>
struct SelectFeatures: boost::enable_if<
boost::mpl::and_<
boost::is_convertible<T, ThisType>,
detail::all_of<
AllTypes,
boost::mpl::or_<
boost::is_convertible<ThisType, _1>,
boost::mpl::not_<
boost::is_convertible<T, _1>
>
>
>
>,
ValueType
> {};