Search code examples
c++constructorinitializer-list

Why can't I use std::vector::emplace_back without explicitly constructing an intermediate object, although instance creation works without one?


I use a class OneOrMoreIntHelper that accepts both a single integer as well as an initializer list of integers, and that works great to construct instances of Class through a single constructor.

However, this fails when trying to use emplace_back. E.g., this code example does not compile, giving the error message

no matching function for call to ‘std::vector::emplace_back()’

#include <initializer_list>
#include <vector>

struct OneOrMoreIntHelper {
    OneOrMoreIntHelper(const int value) {}
    OneOrMoreIntHelper(const std::initializer_list<int> &) {}
};

struct Class {
    Class(OneOrMoreIntHelper) {}
};

void foo() {
    Class single_int(0);
    Class list_two_ints({0, 0});

    std::vector<Class> instances;
    // this works fine
    instances.emplace_back(0);
    
    // error: no matching function for call to ‘std::vector::emplace_back()’
    instances.emplace_back({0, 0}); 
    
    // Workaround
    instances.push_back(OneOrMoreIntHelper{0, 0});
}

I have found the workaround above, but ideally, I would like to not have to instantiate the OneOrMoreIntHelper object at all (in the end, the whole point is to be able to type 0 instead of {0}, so having to type OneOrMoreIntHelper{0, 0} elsewhere complete defeats the purpose).

Complete build log:

main.cpp: In function ‘void foo()’:
main.cpp:22:27: error: no matching function for call to ‘std::vector::emplace_back()’
   22 |     instances.emplace_back({0, 0});
      |     ~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~
In file included from /usr/include/c++/11/vector:72,
                 from main.cpp:2:
/usr/include/c++/11/bits/vector.tcc:109:7: note: candidate: ‘std::vector<_Tp, _Alloc>::reference std::vector<_Tp, _Alloc>::emplace_back(_Args&& ...) [with _Args = {}; _Tp = Class; _Alloc = std::allocator; std::vector<_Tp, _Alloc>::reference = Class&]’
  109 |       vector<_Tp, _Alloc>::
      |       ^~~~~~~~~~~~~~~~~~~
/usr/include/c++/11/bits/vector.tcc:109:7: note:   candidate expects 0 arguments, 1 provided

Solution

  • The reason why instances.emplace_back({0, 0}); fails is that emplace_back is a function template, and you cannot deduce its template parameters from an initializer-list. See also Why doesn't my template accept an initializer list. Specifically, it's not possible because this is a non-deduced context (see this answer, heading 6).

    However, you can write:

    instances.emplace_back(std::initializer_list<int>{0, 0});
    // or
    auto list = {0, 0};
    instances.emplace_back(list); 
    

    This is similar to Emplacement of a vector with initializer list