Article Discussion
Enforcing Code Feature Requirements in C++
Summary: Functions often depend on particular behavioral characteristics (“features”) of code they invoke. For example, thread-safe code must invoke only thread-safe code if it is to remain thread-safe, and exception-safe code must invoke only exception-safe code. This paper describes a technique that enables the specification of arbitrary combinations of user-defined code features on a per-function basis and that detects violations of feature constraints during compilation. The technique applies to member functions (both nonvirtual and virtual), non-member functions, and function templates; operators are excluded.
17 posts on 2 pages.      
« Previous 1 2 Next »
The ability to add new comments in this discussion is temporarily disabled.
Most recent reply: July 20, 2012 5:50 AM by Péter
Péter
Posts: 2 / Nickname: petersohn / Registered: July 4, 2012 9:54 PM
Re: Enforcing Code Feature Requirements in C++
July 5, 2012 3:01 AM      
Interesting concept, but very inefficient. I really could not get over the exponential virtual inharitance tree. I was thinking whether there is a better version, and came up with one that uses implicit conversion rules instead of virtual inheritance. Here is the source code:

#ifndef CODEFEATURES_HPP_
#define CODEFEATURES_HPP_

#include <boost/mpl/vector.hpp>
#include <boost/mpl/placeholders.hpp>
#include <boost/mpl/find.hpp>
#include <boost/mpl/less.hpp>
#include <boost/mpl/copy.hpp>
#include <boost/mpl/fold.hpp>
#include <boost/mpl/sort.hpp>
#include <boost/mpl/remove_if.hpp>
#include <boost/mpl/empty_base.hpp>
#include <boost/mpl/transform.hpp>
#include <boost/mpl/contains.hpp>
#include <boost/mpl/and.hpp>
#include <boost/utility/enable_if.hpp>


namespace CodeFeatures {

using boost::mpl::_1;
using boost::mpl::_2;
using boost::mpl::_;

template<typename S>
struct Features
{
typedef S features_type;

template <typename OtherFeature>
Features(
const OtherFeature&,
typename boost::enable_if<
typename boost::mpl::fold<
typename OtherFeature::features_type,
boost::mpl::true_,
boost::mpl::and_<
boost::mpl::contains<S, _2>,
_1
>
>::type,
int
>::type = 0)
{}
};

}

#endif /* CODEFEATURES_HPP_ */


As it can be seen, it eliminates the need of AllCodeFeatures as well, so it can be generically applied to any feature set. It also works for larger amount of features. For example:


namespace mpl = boost::mpl;
namespace cf = CodeFeatures;

struct F1{};
struct F2{};
struct F3{};
struct F4{};
struct F5{};
struct F6{};
struct F7{};
struct F8{};
struct F9{};
struct F10{};
struct F11{};
struct F12{};

BOOST_STATIC_ASSERT((
boost::is_convertible<
cf::Features<mpl::vector<F4,F5,F6,F7,F8> >,
cf::Features<mpl::vector<F1,F2,F3,F4,F5,F6,F7,F8,F9,F10,F11,F12> >
>::value));

BOOST_STATIC_ASSERT((
boost::is_convertible<
cf::Features<mpl::vector<F1,F2,F3,F4,F5,F6,F7,F8,F9,F10,F11,F12> >,
cf::Features<mpl::vector<F1,F2,F3,F4,F5,F6,F7,F8,F9,F10,F11,F12> >
>::value));

BOOST_STATIC_ASSERT((
!boost::is_convertible<
cf::Features<mpl::vector<F1,F2,F3,F4,F5,F6,F7,F8,F9,F10,F11,F12> >,
cf::Features<mpl::vector<F1,F2,F3,F4,F5,F6,F8,F9,F10,F11,F12> >
>::value));
Péter
Posts: 2 / Nickname: petersohn / Registered: July 4, 2012 9:54 PM
Re: Enforcing Code Feature Requirements in C++
July 20, 2012 5:50 AM      
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
> {};
17 posts on 2 pages.
« Previous 1 2 Next »