AndyG
AndyG

Reputation: 41220

Templated assignment operator failed template instantiation

I'm trying to build a templated base class that allows for assignment to any of its template types, like so:

#include <type_traits>
#include <utility>

template<typename T1, typename... Types>
class Base
{
    public:
    //assignment to base or derived class
    template<typename T, typename std::enable_if<std::is_base_of<Base<T1, Types...>, T>::value>::type = 0>
    Base& operator=(T&& other)
    {
        if (this != &other)
            a = other.a;
        return *this;
    }

    //assignment to other type contained in <T1, Types...>
    template<typename T, typename std::enable_if<!std::is_base_of<Base<T1, Types...>, T>::value>::type = 0>
    Base& operator=(T&& other)
    {
        // do some stuff
        return *this;
    }


    private:
    int a;
};

As you can see, I'm trying to use std::enable_if to differentiate assignment to a Base (move or copy) and assignment to a type by wrapping std::is_base_of. I'm under the impression that T&& should bind to const Base&, Base&&, Base&, as well as const T& T&&, etc.

The problem happens when I try to create a derived class that supports the assignment by simply forwarding the arguments to the base:

class Derived : public Base<int, double>
{
    public:
    // similar pattern here, but call base class' operators
    // for both copy/move assignment
    // as well as assignment to int or double
    template<typename T>
    Derived& operator=(T&& other)
    {
        Base<int, double>::operator=(std::forward<T>(other));
        return *this;
    }
};

As soon as I try to do what I think should be a valid assignment, the compiler tells me that it failed to perform template deduction/substitution.

int main() {
    Derived foo;
    int a = 4;
    foo = a;
    return 0;
}

What am I doing wrong here? Please go easy on me.

Live example

Upvotes: 2

Views: 152

Answers (1)

Piotr Skotnicki
Piotr Skotnicki

Reputation: 48517

Two problems:

  1. For lvalues the forwarding-reference (as well as the template type parameter T itself) becomes an lvalue reference. You should decay type T prior to checking if it's a subclass of other type.

  2. You are using std::enable_if in a wrong way; if you want to utilize ::type = 0 syntax then it needs some other type than the default void one.

Having that said, your std::enable_if syntax should look as follows:

typename std::enable_if<std::is_base_of<Base<T1, Types...>
                                       , typename std::decay<T>::type // decay of T
                                       >::value
                      , int>::type = 0 // int as a second template argument

Complete example:

template<typename T, typename std::enable_if<std::is_base_of<Base<T1, Types...>, typename std::decay<T>::type>::value, int>::type = 0>
Base& operator=(T&& other)
{
    if (this != &other)
        a = other.a;
    return *this;
}

//assignment to other type contained in <T1, Types...>
template<typename T, typename std::enable_if<!std::is_base_of<Base<T1, Types...>, typename std::decay<T>::type>::value, int>::type = 0>
Base& operator=(T&& other)
{
    // do some stuff
    return *this;
}   

Upvotes: 5

Related Questions