Search code examples
c++c++11c++14initializer-listperfect-forwarding

Efficient way of providing an initializer list constructor


Let's say, a class Base with following resorces:

struct Base
{
    int m_int;
    bool m_flag;
    float m_float;
    Base() = delete; // just to see that it didn't call
    Base(int a, bool b, float c): m_int(a), m_flag(b), m_float(c) {}

};

Then I have a SubClass which has a vector of Base class as the resource.

  • After researching I found that using perfect forwarding or forwarding reference, Base class resources can be created much efficiently as it avoids the unnecessary call of default constructors(please correct me, if I am wrong).
  • In addition to the forwarding technique, I wanted also to have an initializer list constructor, so that I can have an aggregate inilitation possible for SubClass

Then I came up with following code for my SubClass.

struct SubClass
{
private:
    std::vector<Base> vec;
public:
    SubClass() = delete; // just to see that it didn't call

    // Idea - 1
    //SubClass(std::initializer_list<Base>&& all): vec(std::move(all)) {}

    // Idea - 2
    SubClass(std::initializer_list<Base> all): vec(all) {}

    // Idea - 3 : but no more list initialization possible
    template<typename ...Arg>
    void addToClass(Arg&& ...args)
    {
        vec.emplace_back(std::forward<Arg>(args)...);
    }
};

Then in the main(), I can have following two possibilities to update resources in SubClass:

int main()
{
    // possibility - 1
    SubClass obj
    {
       {1, true, 2.0f },
       {5, false, 7.0f},
       {7, false, 9.0f},
       {8, true, 0.0f},
    };
    //possibility - 2: without list initialization
    /*obj.addToClass(1, true, 2.0f);
    obj.addToClass(5, false, 7.0f);
    obj.addToClass(7, false, 9.0f);
    obj.addToClass(8, true, 0.0f);*/

    return 0;
}

Question - 1:

Which one of following is efficient or good to use(good practice) in above case? Why?

// Idea - 1
SubClass(std::initializer_list<Base>&& all): vec(std::move(all)) {}

// Idea - 2
SubClass(std::initializer_list<Base> all): vec(all) {}

Question - 2:

Does addToClass(Arg&& ...args) function works similar to any of the above two ideas?


Solution

  • First things first.

    SubClass(std::initializer_list<Base>&& all): vec(std::move(all)) {}
    

    and

    SubClass(std::initializer_list<Base> all): vec(all) {}
    

    Don't do the same thing. The first also needs

    SubClass(std::initializer_list<Base>& all): vec(all) {}
    

    So you can initialize it with an lvalue std::initializer_list (may never happen but it could). Passing by value covers both of those overloads so it is easier to write and it's performance is just about as good (it would take a lot of operations to really make a difference)

    You can't move objects out of a std::initializer_list since the objects as marked as const so

    SubClass(std::initializer_list<Base>&& all): vec(std::move(all)) {}
    

    doesn't actually move anything.

    As for addToClass you can have both. You can have your template version that emaplces a single object into the class. You can also add an overload to take a std::initializer_list so you can add multiple objects like

    void addToClass(std::initializer_list<Base> all)
    {
        vec.insert(vec.end(), all.begin(), all.end());
    }
    

    and that will add all of the objects in all to the end of vec.