C++ – why can't the compiler deduce the types for the function template containing constructor parameters?

I want to use std::aligned_storage_t to decouple allocation and construction, so I write two little helpers to help me do this

#include <type_traits>
template <typename T>
using Uninitialized = std::aligned_storage_t<sizeof(T), alignof(T)>;

template <typename T, typename... Args>
auto get_initialized(Uninitialized<T>& block, Args... args) -> T&
    requires(
        // If we allow type with default constructor, then it would be ambiguous
        // if the caller wants initialization or simply getting reference.
        !std::is_default_constructible_v<T> &&
        (sizeof...(Args) == 0 || std::is_constructible_v<T, Args...>)
    )
{
    T* p = (T*) &block;
    if constexpr (sizeof...(Args) > 0)
        new (p) T{args...};
    return *p;
}

And I tested it with

class Obj {
public:
    Obj() = delete;
    Obj(const char* s) noexcept : m_str(s) {}

    void print() noexcept {
        std::cout << m_str << std::endl;
    }
private:
    const char* m_str = nullptr;
};

int main() {
    Uninitialized<Obj> obj{};
    const char* s = "Test";
    Obj& o = get_initialized(obj, s);
    o.print();
}

And it failed. I’ve tried clang, gcc and msvc, and they all complained about "no matching function call". Complaint from clang

<source>:37:14: error: no matching function for call to 'get_initialized'
    Obj& o = get_initialized(obj, s);
             ^~~~~~~~~~~~~~~

Of course, simply add <Obj> the code would work fine. But I just want to save some typing here. I found output from gcc providing some insight here

<source>:37:29: error: no matching function for call to 'get_initialized(Uninitialized<Obj>&, const char*&)'
   37 |     Obj& o = get_initialized(obj, s);
      |              ~~~~~~~~~~~~~~~^~~~~~~~

So I guess the problem relates to the type of the parameters of constructor, and I’m also quite curious here why would the type of s would be deduced to char*& while it’s just a 8 byte data and can be copied without extra cost. So is there a way to enforce the parameter to be copied, just like std::ref or std::cref?

>Solution :

Uninitialized<T> is an alias…

So your template is actually

using Uninitialized = ;

template <typename T, typename... Args>
auto get_initialized(std::aligned_storage_t<sizeof(T), alignof(T)>& block, Args... args) -> T&

T is not deducible here (any class with same sizeof/alignof would be valid).

Leave a Reply