为何std::visit无法区分模板化重载?命令模式实现遇歧义
我正在实现命令模式,每个命令对应一个预定义接口,接收者可实现多个这类接口,通过CommandList批量应用命令。但代码编译时出现歧义错误,原本期望通过std::visit的类型推导消除重载歧义,但实际未生效。
错误信息
<source>: 在实例化‘void MultiReceiver<Interface, Interfaces>::applyCommandList(CommandList) [with Interface = ICatalogue; Interfaces = {IInventory}; CommandList = std::vector<std::variant<std::shared_ptr<Command<ICatalogue> >, std::shared_ptr<Command<IInventory> > >, std::allocator<std::variant<std::shared_ptr<Command<ICatalogue> >, std::shared_ptr<Command<IInventory> > > > >]’时: <source>:110:26: 要求从这里 <source>:41:53: 错误:请求成员‘applyCommand’有歧义 41 | std::visit([=,this](auto& c){ this->applyCommand(*c); }, command); | ~~~~~~^~~~~~~~~~~~ <source>:30:14: 注:候选函数是:‘void Receiver<Interface>::applyCommand(Command<Receiver<Interface> >) [with Interface = IInventory]’ 30 | void applyCommand(Command<Receiver> command) { | ^~~~~~~~~~~~ <source>:30:14: 注: ‘void Receiver<Interface>::applyCommand(Command<Receiver<Interface> >) [with Interface = ICatalogue]’ Compiler returned: 1
代码实现
#include <cinttypes> #include <functional> #include <memory> #include <iostream> #include <vector> #include <variant> #include <unordered_map> template <class Receiver> class Command { public: virtual void execute(Receiver& receiver) = 0; }; class Product { public: Product(std::string name): _name(name) {}; std::string getName() const { return _name; }; bool operator==(const Product&) const = default; private: std::string _name; }; template<class ...Receivers> using CommandList = std::vector<std::variant<std::shared_ptr<Command<Receivers>>...>>; template <class Interface> class Receiver { public: void applyCommand(Command<Receiver> command) { command.execute(*this); } }; template <class Interface, class ...Interfaces> class MultiReceiver: public Receiver<Interface>, public Receiver<Interfaces>... { public: using CommandList = ::CommandList<Interface, Interfaces...>; void applyCommandList(CommandList commands) { for(const auto& command: commands ) { std::visit([=,this](auto& c){ this->applyCommand(*c); }, command); } }; }; class ICatalogue { public: class AddProductCommand; private: virtual void addProduct(const Product& product) = 0; }; class IInventory { public: class AddItemsCommand; private: virtual void addItems(Product product, uint32_t quantity) = 0; }; class ICatalogue::AddProductCommand: public Command<ICatalogue> { public: AddProductCommand(Product product): _product { product } {}; void execute(ICatalogue& catalogue) { catalogue.addProduct(_product); } private: Product _product; }; class IInventory::AddItemsCommand: public Command<IInventory> { public: AddItemsCommand(Product product, uint32_t quantity): _product {product}, _quantity{quantity} {}; void execute(IInventory& inventory) { inventory.addItems(_product, _quantity); } private: Product _product; uint32_t _quantity; }; class Catalogue: public ICatalogue { private: void addProduct(const Product& product) override { _product.push_back(product); } std::vector<Product> _product {}; }; auto productHash = [](const Product& p){ return std::hash<std::string>{}(p.getName()); }; class Inventory: public IInventory { private: void addItems(Product product, uint32_t quantity) override { _inventory[product] += quantity; }; std::unordered_map<Product, uint32_t, decltype(productHash)> _inventory {10, productHash}; }; class Shop: public Catalogue, public Inventory, public MultiReceiver<ICatalogue, IInventory> { }; int main() { Product banana { "Banana" }; Shop::CommandList commandList { std::make_shared<ICatalogue::AddProductCommand>(banana), std::make_shared<IInventory::AddItemsCommand>(banana, 12) }; Shop shop; shop.applyCommandList(commandList); return 0; }
问题原因分析
同名方法的作用域歧义:
MultiReceiver继承了多个Receiver<Interface>实例(如Receiver<ICatalogue>和Receiver<IInventory>),每个基类都定义了applyCommand方法。C++的名字查找规则会先遍历所有基类作用域找到所有同名方法,再进行参数匹配。由于这些方法的参数类型(Command<Receiver<ICatalogue>>vsCommand<Receiver<IInventory>>)与传入的Command<ICatalogue>/Command<IInventory>完全不匹配,编译器无法通过参数类型筛选出正确方法,因此报歧义错误。核心类型不匹配:
Command模板参数使用的是接口类型(如ICatalogue),但Receiver<Interface>的applyCommand方法参数是Command<Receiver<Interface>>,两者毫无关联。同时Receiver<Interface>并未继承对应的Interface,导致Command的execute方法无法调用接收者的接口方法,即使解决歧义也会触发类型不匹配错误。菱形继承问题:
Shop同时继承Catalogue(实现ICatalogue)和MultiReceiver<ICatalogue, IInventory>(继承Receiver<ICatalogue>),而Receiver<ICatalogue>和Catalogue都继承ICatalogue,会产生菱形继承,导致接口实例重复。
解决办法
步骤1:修复Receiver的基础设计
让Receiver<Interface>虚继承Interface,并修正applyCommand的参数类型为Command<Interface>&(避免多态对象拷贝切割):
template <class Interface> class Receiver : virtual public Interface { public: void applyCommand(Command<Interface>& command) { command.execute(*this); } };
同时让具体接收者类虚继承接口,解决菱形继承问题:
class Catalogue: virtual public ICatalogue { // 实现不变 }; class Inventory: virtual public IInventory { // 实现不变 };
步骤2:消除applyCommand的歧义
有三种可行方案:
方案A:引入基类方法到当前作用域
在MultiReceiver中添加using声明,将所有基类的applyCommand方法引入当前类作用域,让编译器能根据参数类型自动匹配:
template <class Interface, class ...Interfaces> class MultiReceiver: public Receiver<Interface>, public Receiver<Interfaces>... { public: using CommandList = ::CommandList<Interface, Interfaces...>; // 引入所有基类的applyCommand方法 using Receiver<Interface>::applyCommand; using Receiver<Interfaces>::applyCommand...; void applyCommandList(CommandList commands) { for(const auto& command: commands ) { std::visit([this](auto& c){ this->applyCommand(*c); }, command); } }; };
方案B:利用类型推导显式转换this
在std::visit的lambda中,通过Command的类型推导显式转换this到对应的Receiver<Interface>类型:
// 先给Command添加Receiver类型的typedef template <class Receiver> class Command { public: using ReceiverType = Receiver; virtual void execute(Receiver& receiver) = 0; }; // 修改MultiReceiver的applyCommandList template <class Interface, class ...Interfaces> class MultiReceiver: public Receiver<Interface>, public Receiver<Interfaces>... { public: using CommandList = ::CommandList<Interface, Interfaces...>; void applyCommandList(CommandList commands) { for(const auto& command: commands ) { std::visit([this](auto& c){ using CmdType = std::remove_reference_t<decltype(*c)>; using TargetInterface = typename CmdType::ReceiverType; // 显式转换到对应的Receiver子类 static_cast<Receiver<TargetInterface>*>(this)->applyCommand(*c); }, command); } }; };
方案C:重构为模板方法
在MultiReceiver中定义模板版的applyCommand,自动匹配对应的Receiver子类:
template <class Interface, class ...Interfaces> class MultiReceiver: public Receiver<Interface>, public Receiver<Interfaces>... { public: using CommandList = ::CommandList<Interface, Interfaces...>; // 模板方法,根据Command的参数类型自动匹配Receiver template <class T> void applyCommand(Command<T>& command) { static_cast<Receiver<T>&>(*this).applyCommand(command); } void applyCommandList(CommandList commands) { for(const auto& command: commands ) { std::visit([this](auto& c){ this->applyCommand(*c); }, command); } }; };
步骤3:调整Shop的继承
由于Receiver<ICatalogue>已经继承ICatalogue,而Catalogue也实现了ICatalogue,现在Shop只需要继承Catalogue、Inventory和MultiReceiver即可,虚继承已经解决了菱形继承的问题:
class Shop: public Catalogue, public Inventory, public MultiReceiver<ICatalogue, IInventory> { };
内容的提问来源于stack exchange,提问作者Richard Vodden

