Clang não compila código, mas o gcc e o msvc o compilaram

14

Eu não entendo qual é o problema: no meu código ou no compilador (menos possível). Há um pedaço de código como este:

#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;
}

Tanto o GCC quanto o MSVC o compilam. Eu testei no godbolt com versões diferentes do GCC e MSVC 17 (local) e 19. Aqui está um link: https://godbolt.org/z/Enfm6L .

Mas Clang não o compila e emite um erro:

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

E estou interessado - talvez haja alguma parte do padrão em que esse código esteja incorreto ou talvez algo mais.

Andrei
fonte
Como "std :: set :: reverse_iterator" e "std :: set :: dummy_iterator" são definidos nos cabeçalhos de clang?
mvidelgauz 19/02
std :: set :: dummy_iterator não está definido nos cabeçalhos clang (espero). Você pode alterar o dummy_iterator para o que quiser e ele não mudará o resultado, pois o problema não está na definição conforme mostrado abaixo.
Andrei
Obrigado Andrei, eu li a resposta e é realmente interessante
mvidelgauz 19/02

Respostas:

9

Isso provavelmente está relacionado ao CWG 1558 .

O tratamento de argumentos não utilizados em uma especialização de modelo de alias não é especificado pela redação atual de 17.6.7 [temp.alias]. Por exemplo:

  #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);
  }

A referência a first_of com T sendo int equivalente a simplesmente anular ou é uma falha de substituição?

É um defeito que foi abordado desde então, mas se a versão do Clang usada ainda não implementa a correção, ele ainda pode considerar ambas as especializações como simplesmente definindo o segundo argumento como voide não executando todo o discurso de falha de substituição. A solução alternativa é não usar um alias simples std::void_t, mas uma versão um pouco mais complexa

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

Para um modelo de classe (que é o que o apelido agora significa), a falha de substituição é definida. Conectar isso ao seu exemplo apazigua Clang https://godbolt.org/z/VnkwsM .

Contador de Histórias - Monica Sem Calúnia
fonte
11
Outra solução alternativa seria criar características para cada requisito e combiná-las em uma cláusula enable_ifwith std::disjunction(ou a
require
Obrigado pela ajuda e referência! É triste ouvir sobre esse tipo de bug em Clang. Engraçado, no entanto, pensei que sua implementação do void_t é padrão. Não é possível adotar a idéia de alias de modelo.
Andrei