5

I'm trying to conditionally instantiate an extra assignment operator. The code below works fine in clang, but not in gcc 4.7.

The problem I'm having seems very similar the the question asked here: std::enable_if to conditionally compile a member function

The following illustrates the problem I'm having:

#include <type_traits>

template<typename T>
struct StrangerTypeRules;

template<typename T>
struct X;

template< >
struct StrangerTypeRules < unsigned > {
    typedef unsigned type;
};

template< >
struct StrangerTypeRules < bool > {
    typedef X<bool> type;
};

template<typename T>
struct X {
    // In the non-trivial version of my code, I can not use the
    // default assignment operator, therefor I need to define this one
    X& operator=( const X<T>& rhs ) {
        return *this;
    }

    // Alternative assignment oprtator, must only exists if it is
    // different from the assignment operator above
    template<typename =
        typename std::enable_if<
            ( !std::is_same<
                X<T>,
                typename StrangerTypeRules<T>::type
            >::value ),
            X<T>
        >::type
    >
    X<T> & operator=( const typename StrangerTypeRules <T>::type& rhs ) {
        return *this;
    }
};

int main(int argc, const char *argv[])
{
    X<unsigned> x1, x2;

    x1 = 4;
    x2 = x1;

    X<bool> x3, x4; // compile error in gcc 4.7 with -std=c++11
    //x3 = x4;

    return 0;
}

Can this be done in a way which satisfies both clang and gcc 4.7? If so, how?

Compilations error when using gcc:

test.cxx: In instantiation of ‘struct X<bool>’:
test.cxx:52:13:   required from here
test.cxx:38:12: error: no type named ‘type’ in ‘struct std::enable_if<false, X<bool> >’
Steve Vinoski
  • 19,847
  • 3
  • 31
  • 46
Allan
  • 4,562
  • 8
  • 38
  • 59

1 Answers1

2

You need to make the enable_if dependent on a template parameter. As it is now, it is only dependent on the outer template parameter within the template definition. But if you instantiate a class from the outer template, then your assignment operator template that is instantiated into that class is not dependent on a template parameter anymore because T will have been substituted already.

Just introduce a dummy parameter equal to T

template<typename T1 = T, typename =
    typename std::enable_if<
        ( !std::is_same<
            X<T1>,
            typename StrangerTypeRules<T1>::type
        >::value ),
        X<T1>
    >::type
>
X<T1> & operator=( const typename StrangerTypeRules <T1>::type& rhs ) {
    return *this;
}

Using T1 in just one of theplaces within the enable_if<...> template arguments would suffice already, because that already makes the enable_if dependent.

However it is not the call to your operator= that was previously illformed but the declaration of it, which conflicts with the copy-assignment operator, so the enable_if has little utility here. Just replace your code by

template<typename T1 = T>
X<T1> & operator=( const typename StrangerTypeRules <T1>::type& rhs ) {
    return *this;
}

Since this operator= is a template, it will not conflict with the non-template overload. And also because it is a template, when you call it the compiler will prefer the non-template if T is X<T>.

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • Is `typename StrangerTypeRules ::type` deducible here? I thought this would be a non-deducible context. Or does defaulting `T1` to `T` change that? – ildjarn Mar 27 '12 at 21:21
  • @ildjarn it isn't. but it doesn't need to be deduced either, because `T1` has a default argument. After deduction, the default argument is used if `T1` hasn't otherwise been deduced (which will be the case here). – Johannes Schaub - litb Mar 27 '12 at 21:23
  • @ildjarn in this case we could have used `T` in the parameter and return type and stick to `= void` for the default argument. But I like to make the parameter and return types as dependent as possible, because this will make the template as unique as possible and allows more overloads. For example if I would have overloaded it and used `const typename MoreStrangeTypeRules::type&` and it would yield `X` as parameter type, it would still work. Had I used `T`, it would have failed because both overloads would have `const X&` as parameter type at declaration time. – Johannes Schaub - litb Mar 27 '12 at 21:28
  • For that reason, in new code I don't use `typename = typename enable_if<...>::type`, because default template arguments do not influence the signature of a template. Instead I prefer to use `typename enable_if<..., int>::type = 0`. So if I overload with different enable-if expressions each time, it will just work. (Sticking it into the return type has the same effect, but unfortunately cannot be done for constructors and conversion functions). – Johannes Schaub - litb Mar 27 '12 at 21:29