0

I have a set of operators that I need to override for expression templating. I would like all derived classes of a base type match to the base type. Other things would then be caught by a generic type. Unfortunately, the generic type grabs the derived types before the base type does. To make things nice and confusing, everything is templated pretty heavily, including some CRTP. Let me try to give a more simple version of the code:

// Note: 'R' is used for return type
template <typename DerivedType, typename R>
class Base
{ // ...
};

template <typename E1, typename E2, typename R>
class MultOperation : public Base<MultOperation<E1, E2, R>, R>
{ // ...
};

template <typename T>
class Terminal : public Base<Terminal<T>, T>
{ // ...
};

// The broken operators:
template <typename T1, typename T2, typename R1, typename R2>
MultOperation<Base<T1, R1>, Base<T2, R2>, typename boost::common_type<R1, R2>::type>
operator*( Base<T1, R1> const& u, Base<T2, R2> const& v)
{
    return MultOperation<Base<T1, R1>, Base<T2, R2>, typename boost::common_type<R1, R2>::type>(u, v);
}

template <typename T1, typename T2, typename R1, typename R2>
MultOperation<Terminal<T1>, Base<T2, R2>, typename boost::common_type<T1, R2>::type>
operator*( T1 const& u, Base<T2, R2> const& v)
{
    return MultOperation<Terminal<T1>, Base<T2, R2>, typename boost::common_type<T1, R2>::type>(Terminal<T1>(u), v);
}

template <typename T1, typename T2, typename R1, typename R2>
MultOperation<Base<T1, R1>, Terminal<T2>, typename boost::common_type<R1, T2>::type>
operator*( Base<T1, R1> const& u, T2 const& v)
{
    return MultOperation<Base<T1, R1>, Terminal<T2>, typename boost::common_type<R1, T2>::type>(u, Terminal<T2>, v);
}

Now, I can't use any new C++ features. (This is part of some refactors to remove old libraries so we can upgrade to the new cpp standards.) I can use boost stuff, though. I was thinking my answer might lie in boost::enable_if stuff, but all my attempts have led to dead ends. Now, keep in mind that the goal is expression templates, so I can't do any casting stuff for data coming in. Yeah... it's so complicated... I hope you have some magic up your sleeve.

Short version of the question: How can I get (1 * Derived) * Derived to match to operator(T, Base) for the first operator, then operator(Base, Base) for the second operator? It currently matches the first fine, then the second matches to one of the Base-generic operators instead, as T takes no conversion and thereby matches better than Base.

Cory
  • 151
  • 12
  • ins't `class Terminal : public Base, T>` supposed to be `class Terminal : public Base, T>` ? – MikeMB May 12 '15 at 23:16
  • @MikeMB yes. I have changed it now. I wrote the question pretty quick, so there may be a few other small things that are off. @all I am starting to think that an implicit conversion to `Terminal` combined with `enable_if` may hold the answer. Still working on it in cases there are unforeseen problems. – Cory May 12 '15 at 23:23
  • So can you give a set of example inputs, the operator, that is selected by overload resulution and the operator you want to be chosen? – MikeMB May 12 '15 at 23:26
  • if you have two other derived objects A and B (arrays or something) and do `1 * A * B` it should match the first two to the second operator above. That will return the `MultOperation` which, with `B`, should match to the first operator. Currently, the second step would match to the more generic second or third operator instead. – Cory May 12 '15 at 23:35
  • It is starting to look like I will have to overload a boat-load of operators with primitive types instead of doing the generic route... I really don't want to do that. – Cory May 12 '15 at 23:37
  • @Cory The problem is that the overloads with one of the arguments being a template parameter are a better match because they match the type exactly, while the `Base` overloads need a pointer conversion. Do you have specializations for `Base`? If not, perhaps you could provide a typedef or any sort of a tag inside `Base` which will let you test if the `T` inherits from `Base` and do sfinae on this. – Pradhan May 12 '15 at 23:44

2 Answers2

1

Here's a trait that tests whether a class is some kind of Base:

template<class T>
struct is_some_kind_of_Base {
    typedef char yes;
    typedef struct { char _[2]; } no;

    template<class U, class V>
    static yes test(Base<U, V> *);
    static no test(...);

    static const bool value = (sizeof(test((T*)0)) == sizeof(yes));
};

And then constrain your later two operator*s like:

template <typename T1, typename T2,  typename R2>
typename boost::disable_if<is_some_kind_of_Base<T1>,
                MultOperation<Terminal<T1>, Base<T2, R2>, 
                              typename boost::common_type<T1, R2>::type> >::type
operator*( T1 const& u, Base<T2, R2> const& v) { /* ... */ }

Demo.

To prevent common_type from causing a hard error, we need to defer its evaluation.

template <class T1, class T2, class R1, class R2>
struct make_mult_operation {
    typedef MultOperation<T1, T2, typename boost::common_type<R1, R2>::type> type;
};

template <typename T1, typename T2,  typename R2>
typename boost::disable_if<is_some_kind_of_Base<T1>,
                make_mult_operation<Terminal<T1>, T2, T1, R2> >::type::type
operator*( T1 const& u, Base<T2, R2> const& v) { /* ... */ }

Demo.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • Ooo, yes a trait. That looks like it would work nicely. Wish I could +1 twice for the lovely demo too. – Cory May 13 '15 at 16:41
  • Actually, this seems to have the same problem... The variatic parameter list would match before the base type would. Therefore the child classes would be `no` wouldn't they? Here is a tweaked demo that shows the problem: [demo](http://coliru.stacked-crooked.com/a/b6f408b05bb556d1) – Cory May 13 '15 at 17:22
  • @Cory Different problem; the `common_type` is being evaluated too early, causing a hard error. – T.C. May 13 '15 at 17:48
  • It looks to me like `common_type` is being called from the operator that takes one generic and one base class. That would mean that the disable is not cutting out the terminal operator versions. I mean, `common_type` only fails because it is getting `MultOperation` instead of the proper `R` template parameter, which is a side effect of entering the wrong operator. – Cory May 13 '15 at 17:53
  • @Cory The disable kicks in too late in the original version. The logic in the original is like "compute T (=`MultOperation<...>`); is the condition true? If so, SFINAE and remove from overload set; otherwise the return type is T"; the problem is that in this case computing `T` leads to a hard error. The fix is to defer the computation until we've checked the condition. – T.C. May 13 '15 at 18:03
  • Ah, yeah. That makes sense. I was over thinking things again. It makes sense that it would hit the whole template and complain unless you add another step like that. Thanks. – Cory May 13 '15 at 18:04
  • Is there a reason why you don't use `boost::is_base_of`? – davidhigh May 13 '15 at 18:47
  • @davidhigh That doesn't work with templates. (That is, `is_base_of` works to check if something is derived from a particular specialization of `Base`, but not for `Base`.) – T.C. May 13 '15 at 18:49
  • Ah, yes, I was still focusing on my pedagogical implementation and forgot the third template parameter. As an alternative, however, one could typedef the value_type of the base class in the derived and use that for the is_base_of evaluation. Poses more conditions, but avoids those hacks from hell :) – davidhigh May 13 '15 at 19:09
0

I understand your question that you want to specialize a class template for types derived of a given base type. I'll give an example without that many template parameters.

As you suggested, the idea here is to select the overloads via enable_if (I've used the std::enable_if class but you can simply replace std:: by boost:: for your purposes):

template<typename T, typename U>
struct is_derived_from_base
{
    static constexpr bool first = std::is_base_of<Base<T>, T>::value;
    static constexpr bool second = std::is_base_of<Base<U>, U>::value;

    static constexpr bool none = !first && !second;
    static constexpr bool both = first && second;
    static constexpr bool only_first = first && !second;
    static constexpr bool only_second = !first && second;
};

template<typename T, typename U, typename = typename std::enable_if<is_derived_from_base<T,U>::none>::type >
auto operator*(T const& t, U const& u)
{
    std::cout<<"Both T and U are not derived"<<std::endl;
    return MultOperation<T, U>(t,u);
}


template<typename T, typename U, typename = typename std::enable_if<is_derived_from_base<T,U>::only_first>::type >
auto operator*(Base<T> const& t, U const& u)
{
    std::cout<<"T is derived from Base<T>, U is not derived"<<std::endl;
    return MultOperation<Base<T>, U>(t,u);
}

template<typename T, typename U, typename = typename std::enable_if<is_derived_from_base<T,U>::only_second>::type >
auto operator*(T const& t, Base<U> const& u)
{
    std::cout<<"T is not derived, U is derived from Base<U>"<<std::endl;
    return MultOperation<T, Base<U> >(t,u);
}

template<typename T, typename U, typename = typename std::enable_if<is_derived_from_base<T,U>::both>::type >
auto operator*(Base<T> const& t, Base<U> const& u)
{
    std::cout<<"T is derived from Base<T>, U is derived from Base<U>"<<std::endl;
    return MultOperation<Base<T>, Base<U> >(t,u);
}

For more details, see the complete program here.

davidhigh
  • 14,652
  • 2
  • 44
  • 75
  • As mentioned in my question, I cannot use anything past C++98 due to old libraries. This change along with many others are working to remove those old libraries so we can compile with 11+, but it will be a while. As such, I cannot use default template parameters. I will likely alter the code to look more like this answer once we move to 11, but for now, I can't do this stuff. – Cory May 13 '15 at 16:38
  • Ok, I thought "new features" means "no C++11". Could edit, but you got the idea. – davidhigh May 13 '15 at 18:33
  • Nevertheless: replace the template default parameter by a return type, and replace std:: by boost:: and it works. I'd prefer the boost::is_base_of over a hand written version. – davidhigh May 13 '15 at 18:41