Не удается назначить `std::unique_ptr` базовому классу в clang при использовании шаблона псевдонима

#c #clang #c 14 #clang

#c #clang #c 14 #clang

Вопрос:

Следующий код компилируется и отлично работает на gcc 4.9.3 и 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>());
}
  

Однако, если мы изменим значение #if 0 на #if 1 , gcc компилируется, а clang — нет:

 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_ptramp;amp; __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_ptramp; operator=(const unique_ptramp;) = 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
  

В чем проблема с использованием шаблона псевдонима в этом контексте? Или, если gcc более разрешителен, чем должен быть, почему это так?

Ответ №1:

Это проблема CWG 1244:

Пример в 14.4 [temp.type] параграф 1 гласит значительную часть,

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

и говорит, что y и z имеют тот же тип.

Это было бы верно только в том случае, если шаблон псевдонима Z считался эквивалентным шаблону класса Y . Однако 14.5.7 [temp.alias] описывает эквивалентность только для специализаций шаблонов псевдонимов, а не для самих шаблонов псевдонимов. Либо такие правила должны быть указаны, что может быть сложно, либо пример следует удалить.

Мы можем сократить ваш пример до:

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

Это хорошо сформировано тогда и только тогда, когда Buz и Bar считаются эквивалентными. gcc думает, что они есть, clang думает, что это не так. Вопрос о том, каков фактический ответ, по-прежнему остается открытым.