Boost C++ Libraries Home Libraries People FAQ More

PrevUpHomeNext
Making Your Transform Callable

Transforms are typically of the form proto::when< Something, R(A0,A1,...) >. The question is whether R represents a function to call or an object to construct, and the answer determines how proto::when<> evaluates the transform. proto::when<> uses the proto::is_callable<> trait to disambiguate between the two. Proto does its best to guess whether a type is callable or not, but it doesn't always get it right. It's best to know the rules Proto uses, so that you know when you need to be more explicit.

For most types R, proto::is_callable<R> checks for inheritance from proto::callable. However, if the type R is a template specialization, Proto assumes that it is not callable even if the template inherits from proto::callable. We'll see why in a minute. Consider the following erroneous callable object:

// Proto can't tell this defines something callable!
template<typename T>
struct times2 : proto::callable
{
    typedef T result_type;

    T operator()(T i) const
    {
        return i * 2;
    }
};

// ERROR! This is not going to multiply the int by 2:
struct IntTimes2
  : proto::when<
        proto::terminal<int>
      , times2<int>(proto::_value)
    >
{};

The problem is that Proto doesn't know that times2<int> is callable, so rather that invoking the times2<int> function object, Proto will try to construct a times2<int> object and initialize it will an int. That will not compile.

[Note] Note

Why can't Proto tell that times2<int> is callable? After all, it inherits from proto::callable, and that is detectable, right? The problem is that merely asking whether some type X<Y> inherits from callable will cause the template X<Y> to be instantiated. That's a problem for a type like std::vector<_value(_child1)>. std::vector<> will not suffer to be instantiated with _value(_child1) as a template parameter. Since merely asking the question will sometimes result in a hard error, Proto can't ask; it has to assume that X<Y> represents an object to construct and not a function to call.

There are a couple of solutions to the times2<int> problem. One solution is to wrap the transform in proto::call<>. This forces Proto to treat times2<int> as callable:

// OK, calls times2<int>
struct IntTimes2
  : proto::when<
        proto::terminal<int>
      , proto::call<times2<int>(proto::_value)>
    >
{};

This can be a bit of a pain, because we need to wrap every use of times2<int>, which can be tedious and error prone, and makes our grammar cluttered and harder to read.

Another solution is to specialize proto::is_callable<> on our times2<> template:

namespace boost { namespace proto
{
    // Tell Proto that times2<> is callable
    template<typename T>
    struct is_callable<times2<T> >
      : mpl::true_
    {};
}}

// OK, times2<> is callable
struct IntTimes2
  : proto::when<
        proto::terminal<int>
      , times2<int>(proto::_value)
    >
{};

This is better, but still a pain because of the need to open Proto's namespace.

You could simply make sure that the callable type is not a template specialization. Consider the following:

// No longer a template specialization!
struct times2int : times2<int> {};

// OK, times2int is callable
struct IntTimes2
  : proto::when<
        proto::terminal<int>
      , times2int(proto::_value)
    >
{};

This works because now Proto can tell that times2int inherits (indirectly) from proto::callable. Any non-template types can be safely checked for inheritance because, as they are not templates, there is no worry about instantiation errors.

There is one last way to tell Proto that times2<> is callable. You could add an extra dummy template parameter that defaults to proto::callable:

// Proto will recognize this as callable
template<typename T, typename Callable = proto::callable>
struct times2 : proto::callable
{
    typedef T result_type;

    T operator()(T i) const
    {
        return i * 2;
    }
};

// OK, this works!
struct IntTimes2
  : proto::when<
        proto::terminal<int>
      , times2<int>(proto::_value)
    >
{};

Note that in addition to the extra template parameter, times2<> still inherits from proto::callable. That's not necessary in this example but it is good style because any types derived from times2<> (as times2int defined above) will still be considered callable.


PrevUpHomeNext