template TypeEq(A,B) { const bool TypeEq = is(A==B); }
template TypeConverts(A,B) { const bool TypeConverts = is(A:B); }
template TypeConverts_r(A,B) { const bool TypeConverts_r = is(B:A); }

template typelist_check_some(alias CompareOp, T, S...)
{
    static if (S.length==0) { const typelist_check_some = false; }
    else static if ( CompareOp!(T,S[0]) ) { const typelist_check_some = true; }
    else { const typelist_check_some = typelist_check_some!(CompareOp, T, S[1..$]); }
}
template typelist_check_all(alias CompareOp, T, S...)
{
    static if (S.length==0) { const typelist_check_all = true; }
    else static if ( CompareOp!(T,S[0]) ) { 
        const typelist_check_all = typelist_check_all!(CompareOp, T, S[1..$]); 
    }
    else { const typelist_check_all = false; }
}

/// Evaluates to true if is(T==S[i]) is true for some S[i]
template type_equals_some_in(T, S...)
{
    const bool type_equals_some_in = typelist_check_some!(TypeEq, T, S);
}

/// Evaluates to true if is(T:S[i]) is true for some S[i]
template type_colon_some_in(T, S...)
{
    const bool type_colon_some_in = typelist_check_some!(TypeConverts, T, S);
}
/// Evaluates to true if is(S[i]:T) is true for some S[i]
template type_colon_r_some_in(T, S...)
{
    const bool type_colon_r_some_in = typelist_check_some!(TypeConverts_r, T, S);
}
/// Evaluates to true if is(T==S[i]) is true for all S[i]
template type_equals_all_in(T, S...)
{
    const bool type_equals_all_in = typelist_check_all!(TypeEq, T, S);
}
/// Evaluates to true if is(T:S[i]) is true for all S[i]
template type_colon_all_in(T, S...)
{
    const bool type_colon_all_in = typelist_check_all!(TypeConverts, T, S);
}
/// Evaluates to true if is(S[i]:T) is true for all S[i]
template type_colon_r_all_in(T, S...)
{
    const bool type_colon_r_all_in = typelist_check_all!(TypeConverts_r, T, S);
}

template some_in(T...) {
    /// Check that some type in T equals some type in S
    template equal_some_in(S...) {
        static if (S.length==0) { const bool equal_some_in = false; }
        else static if (type_equals_some_in!(S[0],T))  { const bool equal_some_in = true; }
        else { const bool equal_some_in = equal_some_in!(S[1..$]); }
    }

    /// Check that some type in T can convert to some type in S,
    /// I.e  is(T[i]:S[j]) is true for some i,j pair.
    template colon_some_in(S...) {
        static if (S.length==0) { const bool colon_some_in = false; }
        else static if (type_colon_r_some_in!(S[0],T))  { const bool colon_some_in = true; }
        else { const bool colon_some_in = colon_some_in!(S[1..$]); }
    }

    /// Check that some type in T equals all types in S
    template equal_all_in(S...) {
        static if (T.length==0) { const bool equal_all_in = false; }
        else static if (type_equals_all_in!(T[0],S))  { const bool equal_all_in = true; }
        else { const bool equal_all_in = some_in!(T[1..$]).equal_all_in!(S); }
    }
    /// Check that some type in T can convert to all types in S
    /// I.e  for some i, is(T[i]:S[j]) is true for all j.
    template colon_all_in(S...) {
        static if (T.length==0) { const bool colon_all_in = false; }
        else static if (type_colon_all_in!(T[0],S))  { const bool colon_all_in = true; }
        else { const bool colon_all_in = some_in!(T[1..$]).colon_all_in!(S); }
    }
}

template all_in(T...) {
    /// Check that all types in T equal some type in S
    /// That is, for every T[i], there exists some S[j] such that is(T[i]==S[j])
    /// though the j's may vary for each T[i].
    template equal_some_in(S...) {
        static if (T.length==0) { const bool equal_some_in = true; }
        else static if (type_equals_some_in!(T[0],S))  { 
            const bool equal_some_in = all_in!(T[1..$]).equal_some_in!(S);
        }
        else { const bool equal_some_in = false;  }
    }

    /// Check that all types in T can convert to some type in S
    /// That is, for every T[i], there exists some S[j] such that is(T[i]:S[j])
    /// though the j's may vary for each T[i].
    template colon_some_in(S...) {
        static if (T.length==0) { const bool colon_some_in = true; }
        else static if (type_colon_some_in!(T[0],S))  { 
            const bool colon_some_in = all_in!(T[1..$]).colon_some_in!(S);
        }
        else { const bool colon_some_in = false;  }
    }
    /// Check that all types in T equal all types in S
    /// This is here for completeness, but could be useful for the case of 
    /// a single type vs a list, in which case it is equivalent to type_equals_all_in.
    template equal_all_in(S...) {
        static if (T.length==0) { const bool equal_all_in = true; }
        else static if (type_equals_all_in!(T[0],S))  {
            const bool equal_all_in = all_in!(T[1..$]).equal_all_in!(S);
        }
        else { const bool equal_all_in = false;  }
    }
    /// Check that all types in T can convert to all types in S
    /// I.e., for all T[i],  is(T[i]:S[j]) holds for all S[j]
    template colon_all_in(S...) {
        static if (T.length==0) { const bool colon_all_in = true; }
        else static if (type_colon_all_in!(T[0],S))  { 
            const bool colon_all_in = all_in!(T[1..$]).colon_all_in!(S);
        }
        else { const bool colon_all_in = false;  }
    }
}


void main()
{
    assert( type_equals_some_in!(int, float,int));
    assert(!type_equals_some_in!(int, float,double));

    assert( type_colon_some_in!(int, float,double));
    assert(!type_colon_some_in!(cdouble, float,double));

    assert( type_colon_r_some_in!(float, int,float,double));
    assert(!type_colon_r_some_in!(int, float,double));

    assert( type_equals_all_in!(int, int,int));
    assert(!type_equals_all_in!(int, int,float));

    assert( type_colon_all_in!(int, int,float,double));
    assert(!type_colon_all_in!(cdouble, float,double));

    assert( type_colon_r_all_in!(float, int,float,double));
    assert(!type_colon_r_all_in!(int, float,double));

    assert( some_in!(int,float).equal_some_in!(float,double));
    assert(!some_in!(int,float).equal_some_in!(cfloat,double));

    assert( some_in!(int,float).equal_all_in!(float,float));
    assert(!some_in!(int,float).equal_all_in!(cfloat,double));

    assert( some_in!(int,float).colon_some_in!(long,double));
    assert(!some_in!(int,float).colon_some_in!(cfloat,cdouble));

    assert( some_in!(int,float).colon_all_in!(float,double));
    assert(!some_in!(int,float).colon_all_in!(cfloat,double));

    assert( all_in!(int,float).equal_some_in!(int,float,double));
    assert(!all_in!(int,float).equal_some_in!(cfloat,float,double));

    assert( all_in!(int,float).colon_some_in!(long,double,cdouble));
    assert(!all_in!(int,float).colon_some_in!(cfloat,cdouble));

    assert( all_in!(int,int).equal_all_in!(int,int,int));
    assert(!all_in!(int,float).equal_all_in!(cfloat,double));

    assert( all_in!(int,float).colon_all_in!(float,double));
    assert(!all_in!(int,float).colon_all_in!(cfloat,double));

}
