Clang не замечает параметры шаблона по умолчанию
Фон
Согласно стандарту C++, при объявлении типа шаблона вперед с параметрами шаблона по умолчанию каждый из них может появляться только в одном объявлении. Например:
// GOOD example
template <class T = void>
class Example; // forward-declaration
template <class T>
class Example {}; // definition
// GOOD example
template <class T>
class Example; // forward-declaration
template <class T = void>
class Example {}; // definition
// BAD example
template <class T = void>
class Example; // forward-declaration
template <class T = void> // ERROR: template parameter redefines default argument
class Example {}; // definition
проблема
В моем коде много предварительных объявлений в разных файлах, поэтому имеет смысл поместить параметры по умолчанию в определение:
// foo.hpp, bar.hpp, baz.hpp, etc.
template <class T>
class Example;
// example.hpp
template <class T = void>
class Example {};
и, как и ожидалось, все это работает хорошо везде... кроме лязга! Я сузил проблему до этого:
В clang, если шаблон класса имеет параметры по умолчанию, но они не объявлены в первом предварительном объявлении этого класса, и при объявлении экземпляра этого класса угловые скобки не указываются, clang игнорирует параметр по умолчанию и выдает ошибку "нет жизнеспособный конструктор или руководство по выводу для аргументов шаблона... ".
пример
// GOOD example
template <class T>
class Example;
template <class T = void>
class Example {};
int main() {
Example e; // error: no viable constructor or deduction guide for deduction of template arguments of 'Example'
}
- Перемещение
= void
в предварительную декларацию устраняет проблему, но не жизнеспособно для меня, поскольку мои forward-decls находятся в разных файлах, и я не знаю, какой из них появится первым. (Также супер проблематично, так как мои значения по умолчанию будут в каком-то непонятном файле глубоко в базе кода) - Изменение
Example e;
к Example<> e;
решает проблему, но не подходит для меня, так как я разработчик библиотеки и не хочу, чтобы все мои пользователи печатали <>
после моих занятий. - Добавление файла предварительного объявления
example_fwd.hpp
с одним предварительным объявлением и включение его вместо прямого объявления каждый раз устраняет проблему, но я хотел бы избежать этого, если есть лучшее решение.
Вопрос
Кто прав в этом случае: Clang или другие компиляторы? Это ошибка компилятора? Как я могу обойти эту проблему (кроме частичных решений, которые я описал выше)? Я нашел # 10147 (и связанные с ним вопросы о стековом потоке), но он касается параметров шаблона шаблона и также помечен как исправленный более года назад.
редактировать
Это похоже на ошибку, и теперь о ней сообщается на трекере ошибок LLVM (# 40488).
Ответы
Ответ 1
Учитывая следующее:
[temp.param]/12 - Набор стандартных аргументов шаблона, доступных для использования, получается путем объединения аргументов по умолчанию из всех предыдущих объявлений шаблона так же, как аргументы функции по умолчанию: [Пример:
template<class T1, class T2 = int> class A;
template<class T1 = int, class T2> class A;
эквивалентно
template<class T1 = int, class T2 = int> class A;
- конец примера]
Аргументы по умолчанию доступны для
template <class T>
class Example;
template <class T = void>
class Example {};
будут аргументами по умолчанию в определении Example
. Две вышеупомянутые декларации будут эквивалентны одной декларации как
template <class T = void>
class Example {};
что эффективно позволит сделать Example e
.
Оригинальный код должен быть принят. В качестве обходного пути, уже предложенного в ответе max66, вы можете предоставить руководство по выводу, которое использует аргумент по умолчанию
Example() -> Example<>;
Ответ 2
Я не знаю, кто прав, но...
Как я могу обойти эту проблему (кроме частичных решений, которые я описал выше)?
Как насчет добавления следующего правила вычета?
Example() -> Example<>;
Следующий код компилируется (очевидно, С++ 17) как с g++, так и с кланом g++
template <class T>
class Example;
template <class T = void>
class Example {};
Example() -> Example<>;
int main() {
Example e;
}
Ответ 3
Стандарт не делает различий, определяется ли аргумент шаблона по умолчанию в определении или объявлении шаблона.
Поскольку Clang принимает код, когда аргумент по умолчанию появляется в объявлении, но не в определении, по крайней мере один из этих двух вариантов поведения является неправильным. Учитывая [over.match.class.deduct]/1.1.1:
Параметры шаблона - это параметры шаблона C, за которыми следуют параметры шаблона (включая аргументы шаблона по умолчанию) конструктора, если таковые имеются.
Мне хочется сказать, что Clang должен использовать аргумент шаблона по умолчанию.
Я думаю, что вы можете избежать этой ошибки, следуя обычной практике:
-
Если объявление должно быть перенаправлено, создайте специальный заголовочный файл для этого прямого объявления.
-
Определите аргументы по умолчанию в этом файле предварительной декларации
-
Также включите этот файл в заголовочный файл, который обеспечивает определение шаблона.
В качестве примера см. iosfwd
: libstdc++/iosfwd