1

I've been trying to write a zero_sequence type which either creates an index_sequence of a specified number of zeros or uses an existing index_sequence as to produce a new one with the same number of values, but all zero. I'm perplexed as to why this is happening:

template<typename> struct zero_sequence;
template<size_t...I> 
struct zero_sequence<index_sequence<I...>> : index_sequence<(I*0u)...>{};

int main( int argc, char *argv[] ) {

  using A  = index_sequence<0,1,2,3,4>;
  using B  = make_index_sequence<5>;
  using A0 = zero_sequence<A>;
  using B0 = zero_sequence<B>;

  A a;  B b; 

  cout <<< std::is_same<A,B>::value << endl; // is false

  A0 a0; 
  B0 b0;  // < error implicit instantiation of undefined template
  return 0;
}

I don't understand why the two cases are different (on Mac Clang llvm 9.0.0).

In case it is relevant to explaining this behavior, I am using the following implementation of index_sequence and make_index_sequence in C++11 I think I got it from here:

template<typename Integer, Integer... I>
struct integer_sequence {
  using type = integer_sequence;
  using value_type = Integer;
  static constexpr size_t size() noexcept { return sizeof...(I); }
};

template<size_t... Ints> using index_sequence = integer_sequence<size_t, Ints...>;

template <typename,typename> struct _merge_and_renumber;

template <size_t... I1, size_t... I2>
struct _merge_and_renumber<index_sequence<I1...>,index_sequence<I2...>> 
  : index_sequence<I1..., (sizeof...(I1)+I2)...> {};

template <size_t N>
struct make_index_sequence
  : _merge_and_renumber<typename make_index_sequence<N/2>::type,
                        typename make_index_sequence<N - N/2>::type> {};

template<> struct make_index_sequence<0> : index_sequence<> {};
template<> struct make_index_sequence<1> : index_sequence<0> {};
max66
  • 65,235
  • 10
  • 71
  • 111
Greg von Winckel
  • 2,261
  • 2
  • 16
  • 14
  • the only error I get from this is the `<<<` after `cout`? (gcc 7.2 and clang 4.0 both work for me) – Ryan Haining Sep 06 '18 at 23:46
  • actually wait, integer_sequence was added in `C++14`, are you actually using C++11? – Ryan Haining Sep 06 '18 at 23:49
  • live examples: [gcc-5.2](https://wandbox.org/permlink/zvqaRtRwWg0gU9Fp) and [clang 3.5](https://wandbox.org/permlink/wtAgBTM0XlEkq08m) – Ryan Haining Sep 06 '18 at 23:52
  • Yes, the code at the bottom is a `C++11` implementation of `integer_sequence` and `index_sequence`. – Greg von Winckel Sep 06 '18 at 23:52
  • Ah of course, sorry. It seems the problem is just with make_index_sequence implementation. The type of `make_index_sequence` won't be an `index_sequence`, it's a new class type you've defined – Ryan Haining Sep 06 '18 at 23:59
  • 1
    [this one looks right](https://stackoverflow.com/a/49672613/1013719) – Ryan Haining Sep 07 '18 at 00:00
  • Odd, because the `make_index_sequence` either inherits from `index_sequence` or from `_merge_and_renumber`, which, in turn, `inherits from index_sequence` – Greg von Winckel Sep 07 '18 at 00:02
  • @RyanHaining, I replaced the make_index_sequence implementation with the one you linked to and the code now works correctly. Thanks! – Greg von Winckel Sep 07 '18 at 00:08
  • 1
    Right, but they are still different types. Templates type matching doesn't work like function overload resolution, it has to be an exact match. – Ryan Haining Sep 07 '18 at 00:33

1 Answers1

2

I suggest a zero_sequence completely different: not a struct but a using based over a decltype(), a std::declval() and a only declared helper function (following the std::declval() example.

I mean... if you define the following helper function

template <std::size_t ... Is>
constexpr auto zeHelper (index_sequence<Is...> const &)
   -> decltype( index_sequence<(Is,0u)...>{} );

zero_sequence can be defined as

template <typename T>
using zero_sequence = decltype(zeHelper(std::declval<T>()));

and you can declate both a0 and b0

A0 a0; 
B0 b0; // now works

and compile without problems also

static_assert( std::is_same<A0, B0>::value, "!" );

I'm perplexed as to why this is happening [...] I don't understand why the two cases are different

The problem with your zero_sequence template struct specialization

template<size_t...I> 
struct zero_sequence<index_sequence<I...>> : index_sequence<(I*0u)...>{};

is that there is also the generic version (unimplemented, but there is)

template<typename> struct zero_sequence;

So when you use a index_sequence as template parameter for zero_sequence, the implemented specialization matches and is selected.

But when you use a make_index_sequence, that inherit from index_sequence and can be converted to a index_sequence but isn't exactly a index_sequence, the specialization doesn't match and the generic (unimplemented) version of zero_sequence is selected.

Passing through a function, the zeHelper() that I propose or something similar, avoids this problem because a make_index_sequence is convertible to a index_sequence.

max66
  • 65,235
  • 10
  • 71
  • 111