How to make std::make_unique a friend of my class
Asked Answered
P

1

41

I want to declare std::make_unique function as a friend of my class. The reason is that I want to declare my constructor protected and provide an alternative method of creating the object using unique_ptr. Here is a sample code:

#include <memory>

template <typename T>
class A
{
public:
    // Somehow I want to declare make_unique as a friend
    friend std::unique_ptr<A<T>> std::make_unique<A<T>>();


    static std::unique_ptr<A> CreateA(T x)
    {
        //return std::unique_ptr<A>(new A(x)); // works
        return std::make_unique<A>(x);         // doesn't work
    }

protected:
    A(T x) { (void)x; }
};

int main()
{
    std::unique_ptr<A<int>> a = A<int>::CreateA(5);
    (void)a;
    return 0;
}

Right now I get this error:

Start
In file included from prog.cc:1:
/usr/local/libcxx-head/include/c++/v1/memory:3152:32: error: calling a protected constructor of class 'A<int>'
return unique_ptr<_Tp>(new _Tp(_VSTD::forward<_Args>(__args)...));
                           ^
prog.cc:13:21: note: in instantiation of function template specialization 'std::__1::make_unique<A<int>, int &>' requested here
    return std::make_unique<A>(x);     // doesn't work
                ^
prog.cc:22:41: note: in instantiation of member function 'A<int>::CreateA' requested here
std::unique_ptr<A<int>> a = A<int>::CreateA(5);
                                    ^
prog.cc:17:5: note: declared protected here
A(T x) { (void)x; }
^
1 error generated.
1
Finish

What is the correct way to declare std::make_unique as a friend of my class?

Pia answered 24/11, 2015 at 22:24 Comment(9)
try compiling with clang - it complains: main.cpp:17:39: error: friends can only be classes or functions - friend std::unique_ptr<A<T>> std::make_unique<A<T>>();` with the error location being the template instantiation.Desta
Interestingly, compiler with -std=c++14 and this one disappears, and the errors point towards @Praetorian's answer below.Desta
@Desta The above error is with clang. I am on MSVC (vs 2013) now though. Doesn't work there either.Pia
Ok. my bad - different standards versions. Besides this trick being hard to pull off, I question this as a contract design - it seems you are conflating rather that separating concerns here.Desta
Well the main problem is I don't know who to trust. For example, based on Praetorian's answer, it works on clang but not on msvc :PPia
I would agree with the advice given that it is implementation dependant and that you can't rely on it working. Probably time for a redesign :/Desta
You're also pushing your luck if you expect MSVC to be complaint with C++14 - even in the most recent versions. They seem to have more or less got C+11 nailed these days.Desta
@Desta The irony is that a couple of weeks ago I was arguing with a friend about msvc. I kept telling him stuff like "aaaw come on, msvc is fine, you'll see". I am pretty sure I owe him a beer now! :PPia
@Desta Regarding your first comment, make_unique was added in C++14, so if you're using -std=c++11 the compiler has no idea what make_unique you're referring to in the friend declaration.Rahman
R
32

make_unique perfect forwards the arguments you pass to it; in your example you're passing an lvalue (x) to the function, so it'll deduce the argument type as int&. Your friend function declaration needs to be

friend std::unique_ptr<A> std::make_unique<A>(T&);

Similarly, if you were to move(x) within CreateA, the friend declaration would need to be

friend std::unique_ptr<A> std::make_unique<A>(T&&);

This will get the code to compile, but is in no way a guarantee that it'll compile on another implementation because for all you know, make_unique forwards its arguments to another internal helper function that actually instantiates your class, in which case the helper would need to be a friend.

Rahman answered 24/11, 2015 at 22:43 Comment(7)
Indeed that works! But what if I want to pass more arguments to my constructor? If my constructor is for example A(const std::string& str1, const std::shared_ptr& ptr1) which is more complex than an integer.Pia
@Pia I think you'll have to befriend the corresponding make_unique signature, I don't think you get more generic because partial specialization of function templates is not allowed. My suggestion is to forget about make_unique and have CreateA return unique_ptr<A>(new A(...)). Unlike make_shared, make_unique doesn't buy you very many advantages.Rahman
Ok I got it. I just wanted to know if there is a workaround!! Thanks for your answer :)Pia
Is a varadic template friend not allowed?Scholem
@Scholem Yes, in general that's possible. But in this case, if you write template<typename... Args> friend std::unique_ptr<A> std::make_unique<A>(Args&&...);, that's partial specialization of a function template, which is not allowed.Rahman
I tried the same sort of thing with shared_ptr in gcc and, as Praetorian warned might happen, it doesn't work because make_shared doesn't call the constructor itself, but uses a helper function to do so. I think, rather than rely on unguaranteed implementation details, I should give up the slight efficiency gain of make_shared and use std::shared_ptr(new MyClass(...)) instead.Catwalk
What happens if the protected constructor's parameter is A*? The above doesn't seem to work...Draconian

© 2022 - 2024 — McMap. All rights reserved.