You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何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;
}

问题原因分析

  1. 同名方法的作用域歧义:
    MultiReceiver继承了多个Receiver<Interface>实例(如Receiver<ICatalogue>和Receiver<IInventory>),每个基类都定义了applyCommand方法。C++的名字查找规则会先遍历所有基类作用域找到所有同名方法,再进行参数匹配。由于这些方法的参数类型(Command<Receiver<ICatalogue>> vs Command<Receiver<IInventory>>)与传入的Command<ICatalogue>/Command<IInventory>完全不匹配,编译器无法通过参数类型筛选出正确方法,因此报歧义错误。

  2. 核心类型不匹配:
    Command模板参数使用的是接口类型(如ICatalogue),但Receiver<Interface>的applyCommand方法参数是Command<Receiver<Interface>>,两者毫无关联。同时Receiver<Interface>并未继承对应的Interface,导致Command的execute方法无法调用接收者的接口方法,即使解决歧义也会触发类型不匹配错误。

  3. 菱形继承问题:
    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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 06:49:53