Clang не компилирует код, но gcc и msvc скомпилировали его

14

Я не понимаю, в чем проблема: в моем коде или в компиляторе (менее возможно). Вот такой кусок кода:

#include <iostream>
#include <type_traits>
#include <set>


template<typename T, typename = void>
struct TestA: std::false_type {};

template<typename T>
struct TestA<T, std::void_t<typename T::reverse_iterator>> : std::true_type {};

template<typename T>
struct TestA<T, std::void_t<typename T::dummy_iterator>> : std::true_type {};

int main()
{
    std::cout << TestA<std::set<int>>::value;
}

И GCC, и MSVC компилируют его. Я тестировал его на Godbolt с разными версиями GCC и MSVC 17 (локальная) и 19. Вот ссылка: https://godbolt.org/z/Enfm6L .

Но Clang не компилирует его и выдает ошибку:

redefinition of `'TestA<T, std::void_t<typename T::dummy_iterator> >'`

И мне интересно - может быть, есть какая-то часть стандарта, где этот фрагмент кода неправильный, или, может быть, что-то еще.

Андрей
источник
Как "std :: set :: reverse_iterator" и "std :: set :: dummy_iterator" определены в заголовках clang?
Мвидельгауз
std :: set :: dummy_iterator вообще не определен в заголовках clang (я надеюсь). Вы можете изменить dummy_iterator на любой, какой захотите, и это не изменит результат, поскольку проблема не в определении, как показано ниже.
Андрей
Спасибо Андрей, я прочитал ответ, и это действительно интересно
mvidelgauz

Ответы:

9

Это очень вероятно связано с CWG 1558 .

Обработка неиспользованных аргументов в специализации шаблона псевдонима не определена текущей формулировкой 17.6.7 [temp.alias]. Например:

  #include <iostream>

  template <class T, class...>
    using first_of = T;

  template <class T>
    first_of<void, typename T::type> f(int)
      { std::cout << "1\n"; }

  template <class T>
    void f(...)
      { std::cout << "2\n"; }

  struct X { typedef void type; };

  int main() {
    f<X>(0);
    f<int>(0);
  }

Ссылка на first_of с T является int эквивалентной просто void, или это ошибка замещения?

Это дефект, который с тех пор был устранен, но если версия Clang, которую вы использовали, еще не внедрила это исправление, она все равно может рассматривать обе специализации как простое определение второго аргумента void, а не весь спил ошибки замещения. Обходной путь - не использовать простой псевдоним std::void_t, а немного более сложную версию.

template <typename...> struct voider { using type = void; };
template <typename... T> using my_void_t = typename voider<T...>::type;

Для шаблона класса (то, что теперь обозначает псевдоним), определяется ошибка замещения. Включение этого в ваш пример успокаивает Clang https://godbolt.org/z/VnkwsM .

Рассказчик - Unslander Monica
источник
1
Другим обходным решением будет создание черт для каждого требования, а затем объединение их в enable_ifс std::disjunction(или с требованием)
AndyG
Спасибо за помощь и рекомендации! Грустно слышать об этой ошибке в Clang. Забавно, однако, я думал, что ваша реализация void_t является стандартной. Не могу принять идею псевдонима шаблона.
Андрей