9

I am writing a class ptr_scope_manager to manage the creation and destruction of pointers in a given scope. I have studied the answers from this question:

Private constructor inhibits use of emplace[_back]() to avoid a move

And it appears that if I want to manage the creation of an object whose class has a private constructor, my internal std::vector can use push_back but not emplace_back to construct the object. This is because emplace_back uses an internal class to construct the object. That means friending the ptr_scope_manager is not sufficient to allow it to create objects with private constructors.

So what I have done is make two create methods, one for objects with public constructors and one for objects with private constructors that have friended the ptr_scope_manager.

template<typename Type> class ptr_scope_manager { private: std::vector<Type> ptrs; public: template<typename... Args> Type* create_private(Args... args) { ptrs.push_back(Type(args...)); return &ptrs.back(); } template<typename... Args> Type* create_public(Args... args) { ptrs.emplace_back(args...); return &ptrs.back(); } }; class public_ctor { int i; public: public_ctor(int i): i(i) {} // public }; class private_ctor { friend class ptr_scope_manager<private_ctor>; int i; private: private_ctor(int i): i(i) {} // private }; int main() { ptr_scope_manager<public_ctor> public_manager; ptr_scope_manager<private_ctor> private_manager; public_manager.create_public(3); public_manager.create_private(3); // private_manager.create_public(3); // compile error private_manager.create_private(3); } 

My question is this:

Is there any way I can use SFINAE (or otherwise?) to automatically select between create_public() and create_private() based on whether or not the template Type parameter has a public or private constructor? Perhaps utilizing std::is_constructible?

It would be nice to have only one create() method that auto-selects the more efficient create_public() method where possible and falling back on the slightly less efficient create_private when necessary.

3
  • Using std::is_constructible and std::enable_if you should be able to select which method to use. Commented Aug 26, 2014 at 10:49
  • @Joachim That's encouraging but I am new to SFINAE type tricks and I cant get my head round how to proceed. Commented Aug 26, 2014 at 10:52
  • 1
    Just a question: wouldn't private custom allocator calling private (static) methods of the manager solve the problem without the need for SFINAE? That would allow the emplace to work. Commented Aug 31, 2014 at 19:11

3 Answers 3

4

Live demo link.

#include <type_traits> #include <utility> #include <vector> template <typename Type> class ptr_scope_manager { private: std::vector<Type> ptrs; public: template <typename T = Type, typename... Args> auto create(Args&&... args) -> typename std::enable_if<!std::is_constructible<T, Args...>::value, T*>::type { ptrs.push_back(T{ std::forward<Args>(args)... }); return &ptrs.back(); } template <typename T = Type, typename... Args> auto create(Args&&... args) -> typename std::enable_if<std::is_constructible<T, Args...>::value, T*>::type { ptrs.emplace_back(std::forward<Args>(args)...); return &ptrs.back(); } }; class public_ctor { int i; public: public_ctor(int i): i(i) {} // public }; class private_ctor { friend class ptr_scope_manager<private_ctor>; int i; private: private_ctor(int i): i(i) {} // private }; class non_friendly_private_ctor { int i; private: non_friendly_private_ctor(int i): i(i) {} // private }; int main() { ptr_scope_manager<public_ctor> public_manager; ptr_scope_manager<private_ctor> private_manager; ptr_scope_manager<non_friendly_private_ctor> non_friendly_private_manager; public_manager.create(3); private_manager.create(3); // non_friendly_private_manager.create(3); raises error } 
Sign up to request clarification or add additional context in comments.

1 Comment

Sorry this took so long to accept. I was worried about the suggestion that std::is_constructible is implementation dependant regarding detecting public/private. So I was trying to figure out implementing my own is_instantiable<> SFINAE tricks but so far I have not figured out how to get that working. I am sure its possible though.
2

I'm pretty new to SFINAE too, but I think it could be done something like

template<typename... Args> typename std::enable_if<!std::is_constructible<Type, Args...>::value, Type>::type* create(Args... args) { ptrs.push_back(Type(args...)); return &ptrs.back(); } template<typename... Args> typename std::enable_if<std::is_constructible<Type, Args...>::value, Type>::type* create(Args... args) { ptrs.emplace_back(args...); return &ptrs.back(); } 

If Type is not constructible then the first variant will be selected, otherwise the second should be selected.

3 Comments

Accessibility is not considered by OR or SFINAE. Last I checked, anyway.
@PiotrS.: That does not mean it's required to build. It might just happen to build on that compiler with that stdlib.
@Puppy: The SFINAE does not check if manager is friend, but if emplace can or cannot be used (if there is or is not public constructor). The friendship is then needed for the construction in push_back (in case it is not public). ...but to the current: I think that the pointer must be inside the SFINAE, not outside.
2

Note: This is not an answer to the title but to the intent of the author: ...That means friending the ptr_scope_manager is not sufficient to allow it to create objects with private constructors. ...and a proof of my statement in comment: wouldn't private custom allocator calling private (static) methods of the manager solve the problem without the need for SFINAE? That would allow the emplace to work.

IdeOne demo here

#include <deque> #include <memory> #include <iostream> template<class T> class manager { static void construct(T* p, const T& val) { new((void*)p) T(val); } template<class U, class... Args> static void construct(U* p, Args&&... args) { new((void*)p) T(std::forward<Args>(args)...); } class allocator: public std::allocator<T> { public: void construct(T* p, const T& val) { manager::construct(p, val); } template<class U, class... Args> void construct(U* p, Args&&... args) { manager::construct(p, std::forward<Args>(args)...); } //needed for deque ...dunno why it is using rebind for T template<class U> struct rebind { typedef typename std::conditional< std::is_same<T,U>::value, allocator, std::allocator<U>>::type other; }; }; std::deque<T, allocator> storage; //deque preserves pointers public: template<class... Args> T* create(Args&&... args) { storage.emplace_back(std::forward<Args>(args)...); return &storage.back(); } }; class special { friend class manager<special>; int i; special(int i): i(i) {} public: int get() const { return i; } }; int main() { manager<special> m; special* p = m.create(123); std::cout << p->get() << std::endl; } 

2 Comments

Thank you for this solution. Its rather above my comprehension skills right now but I will have fun trying to figure out how it all works!
The trick is to forward the construction to the class you can make a friend - those two static construct methods in manager. It is simple copy-paste from std::allocator::construct. The rebind later is just a workaround for deque implementation in my Cygwin (which can actually be bad, not following the standard, surprised me, but the rebind fixed it).

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.