0

I currently have a class hierachy with the following structure:

#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() {}
}

And I can use them as follows:

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

As you can see, there's a lot of redundancy within the ::addItem() calls. What I would like to be able to do is use them like this:

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

So I'm thinking of using a Variadic Function Template with SFINAE on the ::addItem() function within my abstract base class... The requirements are the following:

  • I will be passing in the address of the object (the function takes a pointer to an object) since the base class stores a list of shared_ptr<Base> objects.
  • I need to make sure that every parameter with the parameter pack of the Variadic Function Template is a derived type of Base.

This is what I have attempted so far:

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
    }
}

I'm trying to convert my normal member function to behave as I described above as I'm thinking of using both Varidiac Function Templates and SFINAE to make sure each argument of the parameter pack is at least derived from Base.

I'm not sure what the proper syntax is nor the set of library functions are to use... I've tried using is_same, is_base_of, conjunction, and other std function templates... And the other issue is how to properly expand the parameter pack in an if statement. Is there a specific notation with the ... or do I have to use fold expressions?

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
Francis Cugler
  • 7,788
  • 2
  • 28
  • 59
  • Adding the pointer to a local object to a `shared_ptr` is a very bad idea. – Dmitry Kuzminov Jul 09 '20 at 02:24
  • @DmitryKuzminov This is temporary... I'm just building the structure of the code interface atm... I'll work on the internal implementation later. Not now, but once I start making my other classes... They will be creating instances of these derived objects... when they get added to the list, each object will have a shared_ptr to the other objects of its own list... – Francis Cugler Jul 09 '20 at 02:25
  • Anyway, you shouldn't use a raw pointer without wrapping it into a smart pointer. That is an error prone design that leads to memory leaks. – Dmitry Kuzminov Jul 09 '20 at 02:28
  • @DmitryKuzminov Eventually, I'll have a class that will store a single instance of each object that is created... When any of those objects add another object to its own internal list, it will store a shared_ptr to the original object... Currently I just don't have that asset or storage class yet... – Francis Cugler Jul 09 '20 at 02:28
  • @DmitryKuzminov I'm completely aware of memory leaks and using raw pointers. Right now I'm not using `new` or `delete` on any of them! I'm just creating local copies in main and passing the address of them to the shared_ptr atm only to test the structure of my class hierarchy... This is all in the prototype stage! – Francis Cugler Jul 09 '20 at 02:30
  • 2
    That is another bad idea because you would get cyclic dependencies. That means that your object would never be destroyed. You should have lists of raw pointers or lists of weak_ptr. – Dmitry Kuzminov Jul 09 '20 at 02:30
  • @DmitryKuzminov that's true... I originally did have `list` and might go back to that... However, the pointers are shared and I kind of do need the reference counts... – Francis Cugler Jul 09 '20 at 02:32

1 Answers1

1

It'd really be a lot easier to just add an overload that takes an initializer_list:

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

And you'd just call it with a braced-init-list: a1.addItem({&a2, &a3, &b1, &b2, &b3});.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • So I wouldn't have to use Variadic Function Template nor worry about, SFINAE then... – Francis Cugler Jul 09 '20 at 02:24
  • This isn't a bad idea, but you are going through the list of `items` that are passed into this function but I will also be traversing through the `list` of items that are stored within the instance itself... Wouldn't this have quadratic search time? Just curious of the impact... – Francis Cugler Jul 09 '20 at 02:38
  • 1
    @FrancisCugler: Won't your variadic function have to do that too, since it's inserting each item into the list? And it's not quadratic; it's O(n*m), where `n` and `m` are the number of items in the list and the number of items given to the function. It's linear with respect to each on their own. – Nicol Bolas Jul 09 '20 at 02:41
  • That's right... I've been at this all day... but I was thinking with `function` templates, most of that would be resolved at compile time... – Francis Cugler Jul 09 '20 at 02:43
  • It's not exactly the answer I had asked on how to do Variadic Function Templates with SFINAE... but it does give me a working solution! – Francis Cugler Jul 09 '20 at 03:02