1

lately I've been toying around with templates and stumbled upon the following problem. I am implementing the CRTP pattern like this:

template<typename derived_t>
struct protocol_object
{
    ...
};

struct data_object : public protocol_object<data_object>
{
    ...
};

I now would like to match instances of class protocol_object in a member template function, while still accepting non CRTP-types:

struct consumer_impl
{
    template<typename derived_t>
    void match(protocol_object<derived_t> &value)
    {
       std::cout << "protocol_class";
    };

    template<typename T>
    void match(T &value)
    {
       std::cout << "any other type";
    };
}

Unfortunately only the second version is ever called. Apparently match(protocol_object<derived_t> &value) is not considered or rejected in favour of the more general form match(T &value).

data_object object;
double value;
consumer_impl consumer;

consumer.match(value);  // yields "any other type" OK
consumer.match(object); // also yields "any other type" but want "protocol_class"

Is there a way out of this?

Thanks for any hints. Arne

4

3 回答 3

2

This isn't related to CRTP. It's a general case of the following:

  • Design a template function, such that all derived classes use a particular specialization.

The issue is that T& value is an exact match for Derived&, while Base& is an inexact match. So we shall make the general form a worse match:

struct conversion_required { conversion_required(int) {} };

template<typename derived_t>
void match_impl(protocol_object<derived_t> &value, int)
{
   std::cout << "protocol_class";
};

template<typename T>
void match_impl(T &value, conversion_required)
{
   std::cout << "any other type";
};

template<typename T>
void match(T& value)
{
    return match_impl(value, 0);
}

Now the specialization, requiring an upcast, is a better match than the general template, requiring a user-defined conversion.

于 2011-10-23T23:53:37.917 回答
0

Overload resolution is performed based on the static type, since it is a compile-time compiler decision. Try this:

consumer.match(static_cast<protocol_object<data_object>&>(object));
于 2011-10-23T23:51:56.080 回答
0

The second function is a better match, as no conversion is required for it, whereas the first function requires a derived-to-base conversion.

You can use boost to overcome this:

template <class T>
void
match (typename boost::enable_if_c
             <boost::is_base_of<protocol_object<T>,T>::value, T>::type& t)
{
    std::cout << "protocol_class";
}

template <class T>
void
match (typename boost::disable_if_c
             <boost::is_base_of<protocol_object<T>,T>::value, T>::type& t)
{
    std::cout << "any other type";
}

This will work for all classes T derived from protocol_object<T>, but not for protocol_object<T> itself. You can add another overload for it (basically, reuse your first match function), or modify the condition in enable_if so that it matches protocol_object<T> too.

于 2011-10-24T00:25:06.730 回答