wyer33 wyer33 - 2 months ago 17
C++ Question

Can't assign a `std::unique_ptr` to a base class in clang when using an alias template

The following code compiles and runs just fine on gcc 4.9.3 and clang 3.7.1

// std::unique_ptr
#include <memory>

// Template class for template-template arguments
template <typename Real>
struct Bar {};

// Base class
template <typename T,template <typename> class XX>
struct Base {};

// Derived class that operates only on Bar
template <typename Real>
struct Derived : public Base <Real,Bar> {};

// Holds the unique_ptr
template <typename T,template <typename> class XX>
struct Foo {
std::unique_ptr <Base <T,XX>> foo;
};

// Create an alias template
template <typename Real>
using Buz = Bar <Real>;

int main() {
#if 0
auto f = Foo <double,Buz> (); //Causes error!
#else
auto f = Foo <double,Bar> ();
#endif
f.foo = std::make_unique <Derived <double>> (Derived <double>());
}


However, if we change the
#if 0
to
#if 1
, gcc compiles, but clang does not:

g++ -std=c++14 test03.cpp -o test03_gcc
clang++ -std=c++14 test03.cpp -o test03_clang
test03.cpp:32:11: error: no viable overloaded '='
f.foo = std::make_unique <Derived <double>> (Derived <double>());
~~~~~ ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.3/include/g++-v4/bits/unique_ptr.h:249:7: note:
candidate function not viable: no known conversion from
'unique_ptr<Derived<double>, default_delete<Derived<double>>>' to
'unique_ptr<Base<double, Buz>, default_delete<Base<double, Buz>>>' for
1st argument
operator=(unique_ptr&& __u) noexcept
^
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.3/include/g++-v4/bits/unique_ptr.h:278:7: note:
candidate function not viable: no known conversion from 'typename
_MakeUniq<Derived<double> >::__single_object' (aka
'unique_ptr<Derived<double> >') to 'nullptr_t' for 1st argument
operator=(nullptr_t) noexcept
^
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.3/include/g++-v4/bits/unique_ptr.h:357:19: note:
candidate function not viable: no known conversion from
'unique_ptr<Derived<double>, default_delete<Derived<double>>>' to
'const unique_ptr<Base<double, Buz>, default_delete<Base<double,
Buz>>>' for 1st argument
unique_ptr& operator=(const unique_ptr&) = delete;
^
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.3/include/g++-v4/bits/unique_ptr.h:264:22: note:
candidate template ignored: disabled by 'enable_if' [with _Up =
Derived<double>, _Ep = std::default_delete<Derived<double> >]
typename enable_if< __and_<
^
1 error generated.
Makefile:2: recipe for target 'all' failed
make: *** [all] Error 1


What's the problem with using the alias template in this context? Or, if gcc is more permissive than it should be, why is that the case?

Answer

This is CWG issue 1244:

The example in 14.4 [temp.type] paragraph 1 reads in significant part,

template<template<class> class TT> struct X { };
template<class> struct Y { };
template<class T> using Z = Y<T>;
X<Y> y;
X<Z> z;

and says that y and z have the same type.

This would only be true if alias template Z were considered to be equivalent to class template Y. However, 14.5.7 [temp.alias] describes equivalence only for specializations of alias templates, not for the alias templates themselves. Either such rules should be specified, which could be tricky, or the example should be deleted.

We can reduce your example down to:

std::unique_ptr<Base<double, Buz>> f = 
    std::make_unique<Base<double, Bar>>();

This is well formed if and only if Buz and Bar are considered equivalent. gcc thinks they are, clang thinks they aren't. It's still an open question as to what the actual answer is.