Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clang doesn't compile code but gcc and msvc compiled it

I don't understand what's the problem: either in my code or in the compiler (less possible). There is a piece of code like this:

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

Both GCC and MSVC compile it. I tested it on godbolt with different version of GCC and MSVC 17(local) and 19. Here is a link: https://godbolt.org/z/Enfm6L.

But Clang doesn't compile it and emits an error:

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

And I'm interested - maybe there is some part of the standard where this piece of code is incorrect or maybe something else.

like image 260
Andrei Avatar asked Feb 19 '20 15:02

Andrei


1 Answers

This is very likely related to CWG 1558.

The treatment of unused arguments in an alias template specialization is not specified by the current wording of 17.6.7 [temp.alias]. For example:

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

Is the reference to first_of with T being int equivalent to simply void, or is it a substitution failure?

It's a defect that was since addressed, but if the version of Clang you used doesn't implement the fix yet, it may still consider both specializations as simply defining the second argument as void, and not doing the whole substitution failure spiel. The workaround is to not use a plain alias std::void_t, but instead a slightly more complex version

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

For a class template (which is what the alias now stands for), the substitution failure is defined. Plugging that into your example appeases Clang https://godbolt.org/z/VnkwsM.

like image 115
StoryTeller - Unslander Monica Avatar answered Oct 30 '22 22:10

StoryTeller - Unslander Monica