2

I hope it's okay to just throw in a piece of code which I don't understand why it is behaving like it is.

I have two problems with the following code.

1) Why is the this pointer for the two instances showing the same value? Here the output of the program:

WJ::WJ(Jit&)
this = 0x7ffff1743950 JV<T, N>::JV(Jit&, indices<Is ...>) [with int ...Is = {0}; T = WJ<float>; int N = 1]
RS<T>::RS(Jit&) [with T = WJ<float>]
this = 0x7ffff1743950 JV<T, N>::JV(Jit&, indices<Is ...>) [with int ...Is = {0}; T = RS<WJ<float> >; int N = 1]
PS<T>::PS(Jit&) [with T = RS<WJ<float> >]
go for it
ptr = 0x7ffff1743950 JV<T, N>::JV(Jit&, JV<T, N>*, indices<Is ...>) [with int ...Is = {0}; T = RS<WJ<float> >; int N = 1]
PS<T>::PS(const PS<T>&) [with T = RS<WJ<float> >; PS<T> = PS<RS<WJ<float> > >]

It shows 2 times the value 0x7ffff1743950. This wonders me because I am sure the first instance is not destroyed before the second is created.

2) I try to make deep copies of an PS where the orig_ptr are set to the original. The here used setup is a recursive template instantiation setup. So orig_ptr on each level should respect the hierarchy and point accordingly. What I don't understand is why does the code compile with F{{(void(Is),j,ptr->F[Is])...}} (it's marked in the code) and why does it not compile with F{{(void(Is),j,&ptr->F[Is])...}}? (which I would assume correct). I don't see which constructor of T (aka RS<WJ<float> >) the compiler is calling. There is no RS<WJ<float> >::RS(Jit&,RS<WJ<float> >) only the pointer version exists.

#include<iostream>
#include<array>

struct Jit {};


template <int... Is>
struct indices {};

template <int N, int... Is>
struct build_indices
  : build_indices<N-1, N-1, Is...> {};

template <int... Is>
struct build_indices<0, Is...> : indices<Is...> {};


template<class T,int N>
struct JV {

  JV(Jit& j) : JV(j,build_indices<N>{}) {}
  template<int... Is>
  JV(Jit& j, indices<Is...>) : 
    jit(j), F{{(void(Is),j)...}} {
    std::cout << "this = " << (void*)this << " " << __PRETTY_FUNCTION__ << "\n";
  }


  JV(Jit& j,JV<T,N>* ptr) : JV(j,ptr,build_indices<N>{}) {}
  template<int... Is>
  JV(Jit& j,JV<T,N>* ptr, indices<Is...>) : 
    // Why does this not compile with &ptr->F[Is] ??
    // What is it calling now (there is no T::T(Jit&,sub_T))
    jit(j), orig_ptr(ptr), F{{(void(Is),j,ptr->F[Is])...}} {  
    std::cout << "ptr = " << (void*)ptr << " " << __PRETTY_FUNCTION__ << "\n";
  }
  std::array<T,N> F;
  JV<T,N>* orig_ptr;
  Jit& jit;
};

template<class T>
struct RS : public JV<T,1>
{
  RS(Jit &j): JV<T,1>(j) {
    std::cout << __PRETTY_FUNCTION__ << "\n";
  }

  RS(Jit &j, RS* orig): JV<T,1>(j,orig) {
    std::cout << "orig = " << orig << " " << __PRETTY_FUNCTION__ << "\n";
  }
};

template<class T>
struct PS : public JV<T,1>
{
  PS(Jit& j): JV<T,1>(j) {
    std::cout << __PRETTY_FUNCTION__ << "\n";
  }

  PS(const PS& rhs) : JV<T,1>(rhs.jit,const_cast<JV<T,1>*>(  static_cast<const JV<T,1>*>(&rhs) ) ) {
    std::cout << __PRETTY_FUNCTION__ << "\n";
  }
};

template<class T>
struct WJ
{
  WJ(Jit& j) {
    std::cout << "WJ::WJ(Jit&)\n";
  }
  WJ(Jit& j,WJ* ptr) {
    std::cout << __PRETTY_FUNCTION__ << "\n";
  }
};

int main() {
  Jit j;
  PS<RS<WJ<float> > > w(j);
  std::cout << "go for it\n";
  PS<RS<WJ<float> > > wcopy(w);
}

** EDIT **

I added the pointer interface to WJ so that the instantiation procedure of F can recurse down the whole way. I thought it might be due to SFINE. But it wasn't.

Tried this with g++-4.7 (Ubuntu/Linaro 4.7.2-4precise1) 4.7.2 with -O0.

** EDIT **

@sehe's answer pointed me in the right direction. Of course, the void(Is) is not needed in the second type of JV constructors. Only in the first type, there it is used to simulate a std::fill. But there we are in the initializer list! The default implementation of the sequence operator helps to eliminate the (void)Is completely.

Now in the 2nd case there is another use of Is, namely in ptr->F[Is], so no need to introduce the artificial void. Now, this looks better:

** EDIT **

JV(Jit& j,JV<T,N>* ptr, indices<Is...>) : 
    jit(j), orig_ptr(ptr), F{{T(j,&ptr->F[Is])...}} { }

It compile and runs fine now!

However, the 1 problem still remain: Why is this 2x the same ?!?

ritter
  • 7,447
  • 7
  • 51
  • 84

2 Answers2

2

The expansion of the template argument pack here:

F {{(void(Is),j,ptr->F[Is])...}}

is very creative, but not what you'd expect.

(void(Is),j,ptr->F[Is])

is a single expression, using the sequence operator (operator,). Which has roughly the same semantics as the following block of 'pseudo function':

{
    (void)Is;
    (void)j;
    return ptr->F[Is];
}

Note how Is and j don't have any other effect than sideeffects, which they have none. So the whole expression is equivalent to

F {{(ptr->F[Is])...}}

And to be honest, I failed to grasp the intent of your code. Here is a little proof of concept I did to verify whether the syntax you seem to be after can work:

#include <iostream>
#include <vector>
#include <array>

typedef std::vector<std::string> Vec;

template <int... I>
    void foo(Vec const& v)
{
    std::array<std::string, sizeof...(I)> expand {{ v.at(I)... }};
    for(auto i: expand)
        std::cout << i << '\n';
}

int main()
{
    const Vec v { "zero", "one", "two", "three", "four", "etc" };
    foo<2,1,3,0>(v);
    foo<42>(v);
}

Outputs:

two
one
three
zero
terminate called after throwing an instance of 'std::out_of_range'
  what():  vector::_M_range_check

So, it does precisely what you'd expect (tested in GCC and Clang++)

sehe
  • 374,641
  • 47
  • 450
  • 633
0

Since there are two prints with this = 0x7ffff1743950 appear before "go for it" they are from constructing w. The one following "go for it" is coming from constructing copyw and I suspect you have additional output after that with this = .

In your JV type you a member:

std::array<T,N> F

and since both PS and RS inherit from JV the type supplied to JV, which processing PS is RS, which mean that the F in the JV that PS is derived from has a PS in which is derived from JV.

Josh Heitzman
  • 1,816
  • 1
  • 14
  • 26