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 должен использовать аргумент шаблона по умолчанию.

Я думаю, что вы можете избежать этой ошибки, следуя обычной практике:

  1. Если объявление должно быть перенаправлено, создайте специальный заголовочный файл для этого прямого объявления.

  2. Определите аргументы по умолчанию в этом файле предварительной декларации

  3. Также включите этот файл в заголовочный файл, который обеспечивает определение шаблона.

В качестве примера см. iosfwd: libstdc++/iosfwd