0
votes

ControllerSender is the base class, and several classes will be direct children of that (only BasicSender is in this example) and instantiated through a factory function MidiControllers::AddSender.

A pointer to the instantiated object, along with other information, is stored in a map. The sequence of building the information in the map is first to obtain the key (id) with AddController and place a new member in the map with default Capabilities. Then AddOutputPtr places in Capabilities for that key a shared_ptr to the output device. Finally AddSender creates a new sender for that key derived from ControllerSender. It is this third step that is failing.

The factory function is throwing this compiler error:

binary '=': no operator found which takes a right-hand operand of type 'std::unique_ptr<BasicSender,std::default_delete<_Ty>>' (or there is no acceptable conversion) with [_Ty=BasicSender]

The failing line is

controllers_.at(id).sender_ = std::make_unique<BasicSender>(ptr);

If I change BasicSender to the base class (ControllerSender) the line compiles without error. I thought this assignment should automatically upcast the pointer, as explained in Is unique_ptr<Derived> to unique_ptr<Base> up-casting automatic?.

How do I fix this?

#include <map>
#include <vector>
#include <JuceLibraryCode/JuceHeader.h>

class ControllerSender {
 public:
   ControllerSender(std::shared_ptr<juce::MidiOutput>& device) : device_(device) {}

 private:
   std::shared_ptr<juce::MidiOutput> device_{};
};

class BasicSender : ControllerSender {
 public:
   using ControllerSender::ControllerSender;
};

class MidiControllers {
 public:
   void AddController(const juce::MidiDeviceInfo& id)
   {
      controllers_.insert({id, Capabilities{}});
   }
   void AddOutputPtr(const juce::MidiDeviceInfo& id, std::shared_ptr<juce::MidiOutput>& device)
   {
      controllers_.at(id).device_ = device;
   }
   void AddSender(const juce::MidiDeviceInfo& id, std::string sender_name)
   {
      auto& ptr = controllers_.at(id).device_;
      if (ptr) {
         if (sender_name == "BasicSender") {
            controllers_.at(id).sender_ = std::make_unique<BasicSender>(ptr);
         }
      }
   }

 private:
   struct Capabilities {
      std::shared_ptr<juce::MidiOutput> device_{nullptr};
      std::unique_ptr<ControllerSender> sender_{nullptr};
   };

   struct IdentifierComp {
      bool operator()(const juce::MidiDeviceInfo& lhs, const juce::MidiDeviceInfo& rhs) const
          noexcept
      {
         return lhs.name < rhs.name || lhs.identifier < rhs.identifier;
      }
   };
   std::map<juce::MidiDeviceInfo, Capabilities, IdentifierComp> controllers_;
};
1
It is possible to assign a unique_ptr<T> to a unique_ptr<U> when T derives from U. But that does requires U to have a virtual destructor. Your base class ControllerSender does not. Also, your BasicSender is using private inheritance instead of public inheritance, is that what you really want?Remy Lebeau

1 Answers

4
votes

The problem is that you used private inheritance:

class BasicSender : ControllerSender

which means that there is no implicit conversion from BasicSender * to ControllerSender *, and therefore no implicit conversion for the corresponding smart pointers.

To fix, use public:

class BasicSender : public ControllerSender

(or use struct keywords instead of class which means default access is public).