#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef long double ld;
typedef pair<ll, ll> pll;
typedef vector<ld> vd;
typedef vector<ll> vll;
typedef vector<string> vstr;
typedef vector<vector<ll>> vvll;
typedef vector<pair<ll, ll>> vpll;
typedef priority_queue<ll, vector<ll>, greater<ll>> spqll;  // 小さい順に取り出し
typedef priority_queue<ll, vector<ll>, less<ll>> bpqll;     // 大きい順に取り出し

#define REP(i, n) for (ll i = 0; i < (ll)(n); i++)
#define FOR(i, a, b) for (ll i = (ll)a; i < (ll)b; i++)
#define IREP(i, v) for (auto i = (v).begin(); i != (v).end(); i++)
#define FI first
#define SE second
#define MP make_pair
#define MT make_tuple
#define PB push_back
#define TS to_string
#define NP next_permutation
#define ALL(v) (v).begin(), (v).end()
#define SZ(x) (ll) x.size()
#define SP(x) setprecision((ll)x)


ll INF = 1e9;
ll MOD = 1000000007;
ll LINF = 1e18;
ld EPS = 1e-9;
ld PI = M_PI;
vll dx = {1, 0, -1, 0, 1, -1, -1, 1};
vll dy = {0, 1, 0, -1, 1, 1, -1, -1};
ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a;} //最大公約数
ll lcm(ll a, ll b) { return a / gcd(a, b) * b;}     //最小公倍数

void yes(){ cout << "Yes" << endl;}
void no(){ cout << "No" << endl;}

//-----------------------------------------
class union_find{
  private:
    ll N; //要素数
    ll ER = 1e18; // x,yが同じ集合にない時の重みの差(場合によって編集)
    vll par; // iの親
    vll rank; // 木の深さ
    vll siz; // iの属する集合の要素数
    vll weig; // iの重さ
    
  public:
    // 要素の初期化
    union_find(ll n){
      N = n;
      par.resize(N); rank.resize(N);
      siz.resize(N); weig.resize(N);
      for(ll i=0;i<N;i++){
        par[i] = i; rank[i] = 0;
        siz[i] = 1; weig[i] = 0;
      }
    }

    // 木の根を求める
    ll root(ll x){
      if(par[x] == x) return x;
      else{
        ll r = root(par[x]);
        weig[x] += weig[par[x]];
        return par[x] = r;
      }
    }

    // 根まで辿って根に対する重みを得る
    ll weight(ll x){
      root(x);
      return weig[x];
    }

    // 木の併合(rank基準) (wはxとyの重みの差 w = w(y) - w(x))
    void unite(ll x, ll y, ll w = 0){
      w += (weight(x) - weight(y));
      x = root(x);
      y = root(y);
      if(x == y) return;

      if(rank[x] < rank[y]){
        par[x] = y;
        siz[y] += siz[x];
        w *= -1;
        weig[x] = w;
      }
      else{
        par[y] = x;
        siz[x] += siz[y];
        weig[y] = w;
        if(rank[x] == rank[y]) rank[x]++;
      }
    }
    // yとxの重みの差 w(y) - w(x)を返す
    ll diff(ll x, ll y){
      if(same(x,y)) return weight(y) - weight(x);
      else return ER; // 同じ集合にない時、ERを返す。
    }

    // xとyが同じ集合に属するかどうか
    bool same(ll x, ll y){
      return root(x) == root(y);
    }

    // xが木の根であるかどうか
    bool is_root(ll x){
      return x == root(x);
    }

    // xの属する集合の要素数
    ll size(ll x){
      return siz[root(x)];
    }

    // 全体でいくつ集合があるか
    ll cnt(){
      ll res = 0;
      for(ll i=0;i<N;i++) if(i == root(i)) res++;
      return res;
    }
};


 
//-----------------------------------------

int main(){
  cin.tie(0);
  ios::sync_with_stdio(false);
  cout << fixed << setprecision(10);

  ll n, q;
  cin >> n >> q;
  union_find tree(n);
  REP(i,q){
    ll com, x, y;
    cin >> com;
    if(com){
      ll x, y;
      cin >> x >> y;
      if(tree.diff(x,y)==LINF) cout << "?" <<endl;
      else cout << tree.diff(x,y) <<endl;
    }
    else{
      ll x, y, z;
      cin >> x >> y >> z;
      tree.unite(x,y,z);
    }
  }

  return 0;
}





Measured memory_bytes: 6016