Search code examples
c++templatesvariadic-templates

passing 'Args' parameters from custom template constructor to container constructor


I'm trying to pass Args parameters from template to container deque.emplace_back() call

eg

class Collection
{
public:
    template <typename ...Args>
    Collection(const std::string& name, Args...  entries)
        : m_name(name)
    {
        m_entries.emplace_back(entries...);  // Here!!!! Entry<int>, Entry<double>, Entry<int, int> added to deque
    }

private:
    std::string m_name;
    std::deque<std::any> m_entries;
};

Collection collection1
{
    "my collection",
    Entry<int>{4},
    Entry<double>{5.5},
    Entry2<int, int>{1, 2}
};


Basically I would like to have multiple `Collection` instances, with different sets of 'Entry'-s (different types, different numbers). And want to make generic constructor for all possible combinations.

emplace_back is template with Args parametes overload

template< class... Args >
reference emplace_back( Args&&... args );

So I'd think that it might take a list of objects. But that is not compiled. With error

/opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/bits/new_allocator.h: In instantiation of 'void std::__new_allocator<_Tp>::construct(_Up*, _Args&& ...) [with _Up = std::any; _Args = {Entry<int>&, Entry<double>&, Entry2<int, int>&}; _Tp = std::any]':
/opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/bits/alloc_traits.h:537:17:   required from 'static void std::allocator_traits<std::allocator<_Tp1> >::construct(allocator_type&, _Up*, _Args&& ...) [with _Up = std::any; _Args = {Entry<int>&, Entry<double>&, Entry2<int, int>&}; _Tp = std::any; allocator_type = std::allocator<std::any>]'
/opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/bits/deque.tcc:170:30:   required from 'std::deque<_Tp, _Alloc>::reference std::deque<_Tp, _Alloc>::emplace_back(_Args&& ...) [with _Args = {Entry<int>&, Entry<double>&, Entry2<int, int>&}; _Tp = std::any; _Alloc = std::allocator<std::any>; reference = std::any&]'
<source>:44:31:   required from 'Collection::Collection(const std::string&, Args ...) [with Args = {Entry<int>, Entry<double>, Entry2<int, int>}; std::string = std::__cxx11::basic_string<char>]'
<source>:58:1:   required from here
/opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/bits/new_allocator.h:187:11: error: no matching function for call to 'std::any::any(Entry<int>&, Entry<double>&, Entry2<int, int>&)'
  187 |         { ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
      |           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Full example on Godbolt https://godbolt.org/z/YoPPToEdr

I'd appreciate advise how to make this work.
Thanks

Solution

  • You don't really want to use emplace_back here. Instead what you want to do is directly initialize the deque in the class member initializer list like you do the string member. That'll give you:

    template <typename ...Args>
    Collection(const std::string& name, Args...  entries) : m_name(name), m_entries{args...} {}
    

    Do note that we use {} in m_entries{args...} because we want to list initialize and curly braces are used for that.


    If you do need to call a function then the way to do that would be

    template <typename ...Args>
    Collection(const std::string& name, Args...  entries)
        : m_name(name)
    {
        (m_entries.emplace_back(entries), ...);
    }
    

    This is call a unary fold expression and the grammar is ( expression_using_pack op ...) or (... op expression_using_pack) depending on if you are doing a right or left fold respectively. Here we use the comma operator to expand out the pack since the operator allows us to process the expressions in a sequenced order without doing anything else.