为什么 std::visit 不能消除模板化重载的歧义

Why can't std::visit disambiguate templated overloads

提问人:Richard Vodden 提问时间:8/7/2023 最后编辑:Richard Vodden 更新时间:8/7/2023 访问量:64

问:

我正在尝试构建一个命令模式,其中每个命令都可以访问定义的接口。接收器实现一个或多个这些接口,然后可以通过应用 CommandLists 来获得命令。我包含了下面的代码和一个编译器资源管理器链接。我希望这两个模板都能应用,创建一个重载集,然后 std::visit(在 lambda 的帮助下)能够根据参数消除歧义,但这显然不是它的工作方式。有人可以解释为什么这不起作用,以及我必须解决哪些选项吗?谢谢!

<source>: In instantiation of '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:   required from here
<source>:41:53: error: request for member 'applyCommand' is ambiguous
   41 |                 std::visit([=,this](auto& c){ this->applyCommand(*c); }, command);
      |                                               ~~~~~~^~~~~~~~~~~~
<source>:30:14: note: candidates are: 'void Receiver<Interface>::applyCommand(Command<Receiver<Interface> >) [with Interface = IInventory]'
   30 |         void applyCommand(Command<Receiver> command) {
      |              ^~~~~~~~~~~~
<source>:30:14: note:                 '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;
}
多态性 C++20 访客 命令模式

评论

0赞 n. m. could be an AI 8/7/2023
也许试着向附近的橡皮鸭(和我们)解释为什么你认为循环的一个迭代会选择一个重载,而另一个迭代会选择另一个重载。
0赞 Richard Vodden 8/7/2023
我以为我有。std::visit 会将解析的类型呈现给 lambda。然后,lambda 将使用重载解析 (en.cppreference.com/w/cpp/language/overload_resolution) 来选择适当的 applyCommand 版本。有点像这样 modernescpp.com/index.php/......
0赞 n. m. could be an AI 8/7/2023
重载解析基于(静态)类型的变量和表达式静态发生。不在(动态)类型的对象上。表达式不能仅仅因为它是循环的一部分而具有两种不同的静态类型。
1赞 Ted Lyngmo 8/7/2023
Sitenote:您的基类需要 dtor。virtual

答:

4赞 user17732522 8/7/2023 #1

这与过载解决方案无关,也与过载解决方案无关。std::visit

失败的是名称查找本身。您的类具有多个具有不同成员函数的基类,因此名称查找是不明确的。类成员的名称查找要求仅在其中一个基类中明确找到该名称。this->applyCommandapplyCommand

如果打算使基类的这些成员中的每一个都可用于派生类中的重载解析,则需要通过派生类中的声明显式地使它们可用:using

using Receiver<Interface>::applyCommand;
using Receiver<Interfaces>::applyCommand...;

但是,它将无法解决重载问题,因为所有重载都不可行。您正在尝试传递给 ,但其参数需要 .Command<SomeInterface>applyCommandCommand<Receiver<SomeInterface>>

评论

0赞 Richard Vodden 8/8/2023
那真是太完美了,谢谢@user17732522!为了让它工作,我必须确保接收器继承了它正在接收的接口(解决您的第二点),然后添加您描述的子句。[godbolt.org/z/j59rxjGoW](工作代码在这里)using
1赞 Ted Lyngmo 8/9/2023
@RichardVodden该链接已断开,则应为:godbolt.org/z/j59rxjGoW。简化可以是只有可变参数包,然后只需要一个等。如果有人想要没有示例,您将收到一条非常清晰的错误消息。MultiReceiverusing ...static_assertMultiReceiverInterface