简体   繁体   中英

How to properly overload operator= when trying to swap variadic templated classes?

I have a class in which I take any number of classes and store them into a std::tuple . It is a variadic templated class. I have properly overloaded the operator+ operator and they work as intended. However, now I am trying to assign one controller class to another. I have tried the following:

template<typename...ClassesR>
auto operator=(const Controller<ClassesR...>& rhs)
{
  objects.swap(rhs.getObjects());
  return *this;
}

However, when I compile the code, I get the error: "No matching function for call to std::tuple<A&,B&>::swap(std::tuple<A&,B&,A&>)

note: no known conversion for argument 1 from std::tuple<A&,B&,A&> to std::tuple<A&,B&>& "

Simply put, I am trying to do the following:

ClassA A(3);
ClassB B(3.4);
ClassC C(4.5f);

Controller<ClassA,ClassB> controllerA(A,B);
Controller<ClassC> controllerB(C);

// thinking about it now, this might be the problem...because controllerA is actually
// Controller<ClassA,ClassB> and not Controller<ClassA,ClassB,ClassA>.
controllerA = controllerA + controllerB; 

//or
//controllerA = controllerB;

Here is the code that I am working with:

#ifndef CONTROLLER_HPP
#define CONTROLLER_HPP

#include <functional>
#include <vector>
#include <utility>
#include <any>

template<typename...Classes>
class Controller
{
  public:
    Controller(Classes&...objects) : objects(objects...){ }
    Controller(std::tuple<Classes&...> tup) : objects(tup){ }

    //...a bunch of code that doesn't matter

    std::tuple<Classes&...> getObjects() const
    {
      return objects;
    }        

    template<typename...ClassesR>
    auto operator=(const Controller<ClassesR...>& rhs)
    {

      objects.swap(rhs.getObjects());

      return *this;
    }

  private:
    std::tuple<Classes&...> objects;

};

template<typename...ClassesL, typename...ClassesR>
auto operator+(const Controller<ClassesL...>& lhs, const Controller<ClassesR...>& rhs)
{
  return Controller(std::tuple_cat(lhs.getObjects(),rhs.getObjects()));
}

template<typename...ClassesL, typename ClassesR>
auto operator+(const Controller<ClassesL...> &lhs, ClassesR rhs)
{
  Controller<ClassesR> makeController(rhs);
  return Controller(std::tuple_cat(lhs.getObjects(),makeController.getObjects()));
}

template<typename ClassesL, typename...ClassesR>
auto operator+(ClassesL lhs, const Controller<ClassesR...> &rhs)
{
  Controller<ClassesL> makeController(lhs);
  return Controller(std::tuple_cat(makeController.getObjects(),rhs.getObjects()));
}


#endif // CONTROLLER_HPP

What is the proper way to overload operator= in this case? As I noted, as I am writing this it is possible that it is because the templated classes are probably set in stone. So Controller<ClassA,ClassB> cannot be modified to Controller<ClassA,ClassB,ClassA> so maybe I need to return a new controller?

In C++, objects cannot ever change their type, and the various specializations of a class template are different, unrelated types. Consider a hypothetical std::vector<int*>::operator=(const std::vector<bool>&) ; it's obvious that this is a meaningless request, and by default that restriction carries over to any class template, even if it has no members whose types depend on the template parameters. (One minor reason for this is that specializations are allowed to have unrelated members—either by inheritance or by explicit or partial specializations.)

As such, it's generally meaningless to define an assignment operator template that accepts any specialization of the containing class template: there's simply nothing that one can do with its data (for most specializations of such a template). Even if the class template is stateless, it's not usually a good idea to weaken the type system by such a definition.

There are of course ways around these restrictions at various costs in terms of syntax and efficiency. A (small) static collection of types (especially of similar size) can be held in a std::variant<…> (which is a union or byte buffer internally); an unbounded set of types can be held in a std::any (which, except for small objects, is a type-erased pointer to a heap allocation internally).

As seen in this question: What are the basic rules and idioms for operator overloading? , the proper way to do this is accept an object by value, not by const reference. So, for example:

auto& operator=(Controller<ClassesR...> rhs)

Part of the reason for this (I'm assuming), as you are finding out, is that if you pass by const & , you are forced to only use const functions. swap() is not a constant function.

Also, auto is never a reference unless you make it one. Thus, to follow the opertor= idiom, you need to return a auto& .

More information on this that explains it far better than I could.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM