c++perfect-forwardingplacement-newexplicit-constructor

Can't add element into container<T> with emplace new if T has explicit constructor


I am writing a fixed size container type, with placement new. When I was testing it I figured it out my "emplace_back()" like function does not compile if the type T has explicit ctor.

Here is a code sample (with missing delete call, etc):

#include <iostream>

template <typename T>
struct TestType
{
   explicit TestType(T value) // removing explicit makes it a compileable code
   {
      std::cout << value << std::endl;
   }
};

template <typename... Args>
void emplace(TestType<int>* buffer, Args&&... args)
{
     // placement new // compile time error in this line
    new (buffer) TestType<int>(std::forward<TestType<int>>(args)...);
}

int main()
{
    TestType<int>* buffer;
    buffer = (static_cast<TestType<int>*>(::operator new(sizeof(TestType<int>) * 10)));
   
    emplace(buffer, 5);
   
    return 0;
}

The erros is: " In instantiation of 'void emplace(TestType*, Args&& ...) [with Args = {int}]': 24:22: required from here 16:64: error: no matching function for call to 'forward(int&)'"

There are some similar questions on stackoverflow, however these are mostly about std::map, probably my question is different. (And if not, I still don't understand what is going on.)

In the case of (e.g.) std::vector emplace_back() works with any T type with explicit ctor in them, why doesn't it work with my container type? This compiles fine:

#include <iostream>
#include <vector>

template <typename T>
struct TestType
{
   explicit TestType(T value)
   {}
};

int main()
{
    std::vector<TestType<int>> vector;
    vector.emplace_back(5);
     
    return 0;
}

Thank you for your help!


Solution

  • You're specifying TestType<int> as template argument for std::forward as std::forward<TestType<int>>(args)..., means you're forwarding arguments as TestType<int>. The argument being passed is tried to be converted to TestType<int> implicitly, which doesn't work because the converting constructor is marked as explicit.

    You should specify Args as template argument for std::forward, i.e. forward the arguments as:

    template <typename... Args>
    void emplace(TestType<int>* buffer, Args&&... args)
    {
        // placement new
        new (buffer) TestType<int>(std::forward<Args>(args)...);
        //                                      ^^^^
    }