Руководства по дедукции и шаблоны классов с переменными конструкторами шаблонов - несоответствующие длины пакета аргументов

Рассмотрим следующее class руководство по определению и выводу:

template <typename... Ts>
struct foo : Ts...
{
    template <typename... Us>
    foo(Us&&... us) : Ts{us}... { }
};

template <typename... Us>
foo(Us&&... us) -> foo<Us...>;

Если я попытаюсь создать экземпляр foo с явными аргументами шаблона код компилируется правильно:

foo<bar> a{bar{}}; // ok

Если я попытаюсь создать экземпляр foo через руководство по удержанию...

foo b{bar{}};
  • g ++ 7 выдает ошибку компилятора:

    prog.cc: In instantiation of 'foo<Ts>::foo(Us ...) [with Us = {bar}; Ts = {}]':
    prog.cc:15:16:   required from here
    prog.cc:5:27: error: mismatched argument pack lengths while expanding 'Ts'
         foo(Us... us) : Ts{us}... { }
                               ^~~
    
  • Clang++5 взрывается:

    #0 0x0000000001944af4 PrintStackTraceSignalHandler(void*) (/opt/wandbox/clang-head/bin/clang-5.0+0x1944af4)
    #1 0x0000000001944dc6 SignalHandler(int) (/opt/wandbox/clang-head/bin/clang-5.0+0x1944dc6)
    #2 0x00007fafb639a390 __restore_rt (/lib/x86_64-linux-gnu/libpthread.so.0+0x11390)
    #3 0x0000000003015b30 clang::Decl::setDeclContext(clang::DeclContext*) (/opt/wandbox/clang-head/bin/clang-5.0+0x3015b30)
    ...
    clang-5.0: error: unable to execute command: Segmentation fault
    

живой пример на wandbox

Хотя clang ++ определенно прослушивается (сообщается как проблема № 32673), правильно ли g ++ отклонять мой код? Мой код плохо сформирован?

1 ответ

Решение

Чтобы еще больше упростить ваш пример, похоже, что GCC не реализует аргументы шаблона переменной в руководствах по выводам:

https://wandbox.org/permlink/4YsacnW9wYcoceDH

Я не видел никаких явных упоминаний о шаблонах переменных в формулировках руководств по выводам в стандарте или на cppreference.com. Я не вижу никакой интерпретации стандарта, который запрещает это. Поэтому я думаю, что это ошибка.

Поскольку foo имеет конструктор, компилятор генерирует неявное руководство по выводу на основе конструктора:

// implicitly generated from foo<T...>::foo<U...>(U...)
template<class... T, class... U> foo(U...) -> foo<T...>;

template<class... T> foo(T...) -> foo<T...>; // explicit

Проблема в том, что gcc предпочитает неявное руководство и, следовательно, T в {} а также U в {bar}; Clang (начиная с 5.0.0 в соответствии с Godbolt) предпочитает явное руководство. Это проблема разрешения перегрузки; когда две направляющие вычитания оказываются неоднозначными, явные направляющие вычитания предпочтительнее неявных направляющих. Но Clang и GCC не согласны с тем, являются ли руководства по выводам неоднозначными:

template<class... T, class... U> int f(U...) { return 1; }
template<class... T> int f(T...) { return 2; }
int i = f(1, 2);

Эта программа (вообще не включающая руководства по выводам) принимается gcc (выбор #1) и отклоняется clang (как неоднозначный). Возвращаясь к нашим шагам, это означает, что возвращаясь к руководствам по дедукции, Clang получает возможность связать неоднозначность, выбирая явное руководство по выводу по неявному руководству по выводу (сгенерированному из шаблона конструктора), в то время как gcc не может сделать это, поскольку он уже выбрал неявное руководство вычета в качестве предпочтительного кандидата.

Мы можем построить еще более простой пример:

template<class... T, int = 0> int f(T...);  // #1
template<class... T> int f(T...);  // #2
int i = f(1, 2);

Опять же, gcc (неверно) выбирает # 1, в то время как clang отвергает неоднозначность.

Важно отметить, что мы можем обойти эту проблему, добавив еще одно явное руководство по выводу, которое, тем не менее, gcc предпочтет неявному руководству по выводу, сгенерированному из конструктора:

template <typename U, typename... Us>
foo(U&& u, Us&&... us) -> foo<U, Us...>;

Это является предпочтительным (когда предоставляется более 0 аргументов), поскольку он связывает первый аргумент с единственным параметром, а не пакетом. В случае с 0 аргументами не имеет значения, какое руководство по выводу (между исходным явным руководством и неявно созданным руководством) выбрано, поскольку оба дают одинаковый результат, foo<>, Можно безопасно добавить это для всех компиляторов, так как это предпочтительнее в случае аргумента 1+ и не является кандидатом в случае аргумента 0 аргумента.

Пример.

Другие вопросы по тегам