5

Consider the following code snippet where we we're trying to deduce the template parameter to the function foobar():

struct Bar { static constexpr auto size = 5; };

template <class T, class=std::make_index_sequence<T::size>>
struct FooList;
template <class T, std::size_t... Idxs>
struct FooList<T, std::integer_sequence<std::size_t, Idxs...>> { };

struct Wrapper {
  template <class T>
  using list_type = FooList<T>;
};

template <class T>
void foobar(typename Wrapper::template list_type<T>) { }

void test() { 
  foobar(FooList<Bar>{});
}

The major compiler frontends disagree on whether this should compile (see this godbolt). If we give the template parameter explicitly, like this:

void test_explicit() { 
  foobar<Bar>(FooList<Bar>{});
}

all of the major frontends compile the code successfully (godbolt). Interestingly, if we make the deduction simpler:

struct Bar { };

struct Wrapper {
  template <class T>
  using my_type = T;
};

template <class T>
void foobar(typename Wrapper::template my_type<T>) { }

void test() { 
  foobar(Bar{});
}

a different subset of the frontends compile and fail to compile (MSVC being the only one that compiles both, see this godbolt).

Which implementation is conforming here, and which are buggy (and why)? Can anyone find the portion of the C++ standard that specifies how this should work?

NathanOliver
  • 161,918
  • 27
  • 262
  • 366
Daisy Sophia Hollman
  • 5,488
  • 6
  • 22
  • 32
  • 1
    I've added the language-lawyer tag as it looks like you want supporting documentation. fell free to remove it if you fell that is to restrictive. – NathanOliver Jul 25 '19 at 15:58
  • I don't think compilers should accept your code at all. You use desambiguator keywords `typename` and `template` on a non dependent expression. In fact, your second example compiles if you remove them. – Guillaume Racicot Jul 25 '19 at 15:59
  • @GuillaumeRacicot I suspect permitting extra disambiguators was added to the standard about when they also reduced the places you needed disambiguators, to make removing disambiguators being required not break builds. But I don't have chapter and verse, nor am I certain. – Yakk - Adam Nevraumont Jul 25 '19 at 16:11
  • As for the question, I recall there being an issue with template aliases that perfectly match or not, and if `template using trivial=T` would ever match `template – Yakk - Adam Nevraumont Jul 25 '19 at 16:14
  • @GuillaumeRacicot It was my understanding that compilers mostly treat disambiguators as noise in parameters; I vaguely remember something about a change in 14 or 17 that allowed this for consistency. This also doesn't explain why the explicit template argument version works. – Daisy Sophia Hollman Jul 25 '19 at 16:28
  • @Yakk-AdamNevraumont I'm sure it has something to do with perfect matches (especially given the error message from clang in the first example), but I'm not clear about (1) when that applies and (2) whether this is supposed to be a perfect match. – Daisy Sophia Hollman Jul 25 '19 at 16:30
  • In case someone else comes across this, here's a [workaround](https://godbolt.org/z/aNnPl3), at least for my use case. – Daisy Sophia Hollman Jul 25 '19 at 16:44
  • 2
    Filed [llvm 42757](https://bugs.llvm.org/show_bug.cgi?id=42757) for the first example and [gcc 91262](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=91262) for the second example. – Barry Jul 25 '19 at 18:30
  • @Barry: Can you help me understand how `Wrapper::type` not a dependent name? – AndyG Jul 26 '19 at 00:13
  • 1
    @AndyG: Of course it’s dependent, but since `Wrapper` is not dependent, `Wrapper::type` isn’t, and so it’s known to be a type for any `T`. That’s all that’s needed at the `typename`/`template` level. – Davis Herring Jul 26 '19 at 01:17
  • https://stackoverflow.com/questions/57133186/g-and-clang-different-behaviour-when-stdmake-index-sequence-and-stdin is another case of the first issue (std::make_index_sequence) – ecatmur Nov 03 '20 at 11:15

1 Answers1

0

As discussed in the comments, this is a clang bug in the implementation of __make_integer_seq. I wanted to offer a workaround:

#include <utility>

namespace workaround {
#ifdef __clang__
  template<class T, T N>
  struct make_index_sequence_helper {
      using type = __make_integer_seq<std::integer_sequence, T, N>;
  };

  template<class T, T N>
  using make_integer_sequence = typename make_index_sequence_helper<T, N>::type;

  template<std::size_t N>
  using make_index_sequence = make_integer_sequence<std::size_t, N>;
#else
  using std::make_integer_sequence;
  using std::make_index_sequence;
#endif
}

Then using it in FooList works again:

template <class T, class=workaround::make_index_sequence<T::size>>
struct FooList;
template <class T, std::size_t... Idxs>
struct FooList<T, std::integer_sequence<std::size_t, Idxs...>> { };
unddoch
  • 5,210
  • 1
  • 23
  • 36