3

I'm doing some template-meta-programming and I would like to implement a generic cloning function that selects a method of cloning depending on the validity of expressions via SFINAE (Substitution Failure Is Not An Error).

On this reference website it says that

The function

make_unique<T>( std::forward<Args>(args)... )

is equivalent to:

unique_ptr<T>(new T(std::forward<Args>(args)...))

Does this mean that the following code

template <typename T>
auto my_clone( const T & t ) -> decltype( std::make_unique<T>(t) )
{
    return std::make_unique<T>(t);
}

should be completely equivalent to

template <typename T>
auto my_clone( const T & t ) -> decltype( std::unique_ptr<T>( new T(t) ) )
{
    return std::unique_ptr<T>( new T(t) );
}

even when I have other overloads of the function my_clone? In other words: is std::make_unique() SFINAE-friendly?

If T is not copy constructible, then the latter code would not participate in overload resolution due to SFINAE.

Here's a small example that fails to compile on GCC 5.3 with C++14 turned on:

#include <memory>

// It does **not** work with this snippet:
template <typename T>
auto my_clone( const T & t ) -> decltype( std::make_unique<T>( t ) )
{
    return std::make_unique<T>( t );
}

/* // But it works with this snippet instead:
template <typename T>
auto my_clone( const T & t ) -> decltype( std::unique_ptr<T>( new T(t) ) )
{
    return std::unique_ptr<T>( new T(t) );
}*/

// This is another overload for testing purposes.
template <typename T>
auto my_clone( const T & t ) -> decltype(t.clone())
{
    return t.clone();
}  

class X
{
public:
    X() = default;

    auto clone() const
    {
        return std::unique_ptr<X>( new X(*this) );
    }

private:
    X( const X & ) = default;
}; 

int main()
{
    // The following line produces the compiler error: 
    // "call to 'my_clone' is ambiguous"
    const auto x_ptr = my_clone( X() ); 
}
Community
  • 1
  • 1
Ralph Tandetzky
  • 22,780
  • 11
  • 73
  • 120
  • 1
    The standard does not guarantee it, so you should not rely on it. – Holt Apr 28 '16 at 13:05
  • 1
    @Holt What doesn't the standard not gaurantee? in [unique.ptr.create] it states that `make_unique` *Returns*: `unique_ptr(new T(std::forward(args)...))`. – NathanOliver Apr 28 '16 at 13:08
  • 1
    @NathanOliver Yes but the standard does not guarantee that `std::make_unique(Args&&...)` should not exist if `T` is not constructible using `Args&&...`. – Holt Apr 28 '16 at 13:10
  • @Holt Ah okay. I just wasn't sure exactly what you were talking about. – NathanOliver Apr 28 '16 at 13:11
  • 1
    In your case, you may use `std::is_copy_constructible` traits instead. – Jarod42 Apr 28 '16 at 13:27

1 Answers1

6

The standard only guarantees that:

template <class T, class... Args> unique_ptr<T> std::make_unique(Args&&... args);

...must returns unique_ptr<T>(new T(std::forward<Args>(args)...)), it does not guarantee that the make_unique function should only exist if T is constructible using Args..., so it is not SFINAE friendly (per the standard), so you cannot rely on it.

The only section of the standard mentioning make_unique:

§20.8.1.4 [unique.ptr.create]:

template <class T, class... Args> unique_ptr<T> make_unique(Args&&... args);
  1. Remarks: This function shall not participate in overload resolution unless T is not an array.
  2. Returns: unique_ptr<T>(new T(std::forward<Args>(args)...)).

In your case, you may want to either use the version with std::unique_ptr<T>(new T(...)) or use is_copy_constructible to make your my_clone SFINAE friendly (@Yakk, @Jarod42), e.g.:

template <typename T,
          typename = std::enable_if_t<std::is_copy_constructible<T>::value>>
auto my_clone(const T & t) -> decltype(std::make_unique<T>(t)) {
    return std::make_unique<T>(t);
}
Holt
  • 36,600
  • 7
  • 92
  • 139