【问题标题】:Using SFINAE to ensure the Variadic Parameter Pack Arguments are Derived of a Specific Type使用 SFINAE 确保可变参数包参数派生为特定类型
【发布时间】:2020-10-29 13:05:47
【问题描述】:

我目前有一个具有以下结构的类层次结构:

#include <list>
#include <memory>

class Base {
protected:
    std::list<std::shared_ptr<Base>> items_;
    Base(){}
public:
    addItem(Base* derived) { 
        // adds derived to items_
        // and checks for duplicates
    }
};

class DerivedA : public Base {
public:
    DerivedA() {}
}

class DerivedB : public Base {
public:
    DerivedB() {}
}

我可以按如下方式使用它们:

int main() { 
    DerivedA a1, a2, a3;
    DerivedB b1, b2, b3;

    a1.addItem(&a2); a1.addItem(&a3); a1.addItem(&b1); a1.addItem(&b2); a1.addItem(&b3);
    a2.addItem(&a1); a2.addItem(&a3); a2.addItem(&b1); a2.addItem(&b2); a2.addItem(&b3);
    a3.addItem(&a1); a3.addItem(&a2); a3.addItem(&b1); a3.addItem(&b2); a3.addItem(&b3);       

    b1.addItem(&a1); b1.addItem(&a2); b1.addItem(&a3); b1.addItem(&b2); b1.addItem(&b3);
    b2.addItem(&a1); b2.addItem(&a2); b2.addItem(&a3); b2.addItem(&b1); b2.addItem(&b3);
    b3.addItem(&a1); b3.addItem(&a2); b3.addItem(&a3); b3.addItem(&b1); b3.addItem(&b2);

    return 0;
}

如您所见,::addItem() 调用中有很多冗余。我希望能够像这样使用它们:

int main() {
    DerivedA a1, a2, a3;
    DerivedB b1, b2, b3;

    a1.addItem(&a2, &a3, &b1, &b2, &b3);
    a2.addItem(&a1, &a2, &b1, &b2, &b3);
    a3.addItem(&a1, &a2, &b1, &b2, &b3);

    b1.addItem(&a1, &a2, &a3, &b2, &b3);
    b2.addItem(&a1, &a2, &a3, &b1, &b3);
    b3.addItem(&a1, &a2, &a3, &b1, &b2);

    return 0;
}

所以我正在考虑在我的抽象基类中的 ::addItem() 函数上使用带有 SFINAE 的可变参数函数模板...要求如下:

  • 我将传入对象的地址(该函数接受一个指向对象的指针),因为基类存储了shared_ptr&lt;Base&gt; 对象的列表。
  • 我需要确保带有可变参数函数模板参数包的每个参数都是Base 的派生类型。

这是我到目前为止所尝试的:

template<typename... Args, std::enable_if_t<std::is_base_of_v<Base, Args>...> { // How to use SFINAE here?
void addItem(Args*&& ... args) {
    for ( auto& it : items_ ) { 
        // I also need to check a member variable from each of the `Args...` or derived types
        // ::foo() is not shown in the above classes but exists in my code (its not important here)
        // what is important is how do I expand (args) within an if statement?
        if ( args->foo() == l->foo() ) { 
            // do something
        }
        // add to list
    }
}

我正在尝试将我的普通成员函数转换为如上所述的行为,因为我正在考虑使用 Varidiac 函数模板和 SFINAE 来确保参数包的每个参数至少派生自 Base。

我不确定正确的语法是什么,也不确定要使用的库函数集...我尝试过使用is_same、is_base_of、conjunction 和其他 std 函数模板...另一个问题是如何在 if 语句中正确扩展参数包。 ... 是否有特定的符号,还是我必须使用折叠表达式?

【问题讨论】:

  • 将指向本地对象的指针添加到 shared_ptr 是一个非常糟糕的主意。
  • @DmitryKuzminov 这是临时的……我只是在构建代码接口 atm 的结构……我稍后会处理内部实现。不是现在,但是一旦我开始制作我的其他类......他们将创建这些派生对象的实例......当它们被添加到列表中时,每个对象都将有一个 shared_ptr 到它自己列表中的其他对象...... .
  • 无论如何,你不应该使用原始指针而不将其包装成智能指针。这是一个容易出错的设计,会导致内存泄漏。
  • @DmitryKuzminov 最终,我将有一个类来存储所创建的每个对象的单个实例......当这些对象中的任何一个将另一个对象添加到它自己的内部列表中时,它将存储原始对象的 shared_ptr...目前我还没有那个资产或存储类...
  • 这是另一个坏主意,因为你会得到循环依赖。这意味着您的对象永远不会被破坏。你应该有原始指针列表或weak_ptr列表。

标签: c++ c++17 variadic-templates sfinae fold-expression


【解决方案1】:

添加一个采用initializer_list 的重载确实容易得多:

addItem(std::initializer_list<Base*> items)
{
    for(auto item: items)
        addItem(item);
}

您只需使用花括号初始化列表调用它:a1.addItem({&amp;a2, &amp;a3, &amp;b1, &amp;b2, &amp;b3});。

【讨论】:

  • 所以我不必使用可变参数函数模板也不必担心,那么 SFINAE...
  • 这不是一个坏主意,但是您正在浏览传递给此函数的 items 列表,但我还将遍历存储在其中的项目的 list实例本身......这不会有二次搜索时间吗?只是好奇影响...
  • @FrancisCugler:您的可变参数函数是否也必须这样做,因为它将每个项目插入到列表中?而且它不是二次的;它是 O(n*m),其中 n 和 m 是列表中的项目数和赋予函数的项目数。它对每个人都是线性的。
  • 没错...我整天都在做这个...但我在想function模板,其中大部分将在编译时解决...
  • 这不是我问过的关于如何使用 SFINAE 进行可变参数函数模板的答案......但它确实给了我一个可行的解决方案!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-01-15
  • 2023-03-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-18
  • 1970-01-01
相关资源
最近更新 更多