¿Por qué las instancias de plantillas continúan para siempre aquí?

En el siguiente código, quiero reemplazar

template <typename T, typename... Args>
auto check (rank<1,T>, Args... args) const
        -> std::enable_if_t<!has_argument_type<T, Args...>(), decltype(check(rank<2, Ts...>{}, args...))> {
    return check(rank<2, Ts...>{}, args...);  // Since rank<1,T> derives immediately from rank<2, Ts...>.
}

template <typename T, typename... Args>
auto check (rank<2,T>, Args... args) const
        -> std::enable_if_t<!has_argument_type<T, Args...>(), decltype(check(rank<3, Ts...>{}, args...))> {
    return check(rank<3, Ts...>{}, args...);  // Since rank<2,T> derives immediately from rank<3, Ts...>.
}
// etc... until rank<9>.

con el simple

template <std::size_t N, typename T, typename... Args>
auto check (rank<N,T>, Args... args) const
        -> std::enable_if_t<!has_argument_type<T, Args...>(), decltype(check(rank<N+1, Ts...>{}, args...))> {
    return check(rank<N+1, Ts...>{}, args...);  // Since rank<N,T> derives immediately from rank<N+1, Ts...>.
}

template <typename T, typename... Args>
auto check (rank<10, T>, Args... args) const { std::cout << "Nothing found.\n"; }

Pero cuando lo hago, las instancias de plantilla continúan para siempre a pesar de la finalizacióncheck(rank<10, T>, Args... args) función. Aquí está el código completo usando la versión larga anterior. Perdón por no minimizar el problema porque no creo que pueda minimizarlo y mostrar cuál es el problema. Saltar a main () te mostrará la tarea simple que busco, pero quiero resolverla usando la clasificación de secuencia y la resolución de sobrecarga.

#include <iostream>
#include <type_traits>
#include <tuple>

template <typename T, typename... Args>
constexpr auto has_argument_type_impl(int)
    -> decltype(std::is_same<typename T::argument_type, std::tuple<Args...>>{});  // Checking both that T::argument_type exists and that it is the same as std::tuple<Args...>.

template <typename T, typename... Args>
constexpr auto has_argument_type_impl(long) -> std::false_type;

template <typename T, typename... Args>
constexpr bool has_argument_type() { return decltype(has_argument_type_impl<T, Args...>(0))::value; }

template <typename T, std::size_t N, typename... Args>
constexpr auto has_argument_type_n_impl(int)
    -> decltype(std::is_same<typename T::template argument_type<N>, std::tuple<Args...>>{});  // Checking both that T::argument_type<N> exists and that it is the same as std::tuple<Args...>.

template <typename T, std::size_t N, typename... Args>
constexpr auto has_argument_type_n_impl(long) -> std::false_type;

template <typename T, std::size_t N, typename... Args>
constexpr bool has_argument_type_n() { return decltype(has_argument_type_n_impl<T, N, Args...>(0))::value; }

template <typename... Ts>
class Factory {
    template <std::size_t, typename...> struct rank;

    template <std::size_t N, typename First, typename... Rest>
    struct rank<N, First, Rest...> : rank<N, Rest...> {};

    template <std::size_t N, typename T> struct rank<N,T> : rank<N+1, Ts...> {};

    template <typename T> struct rank<10, T> {};  // Need to end the instantiations somewhere.
public:
    template <typename... Args>
    decltype(auto) create (Args... args) const {
        return check(rank<0, Ts...>{}, args...);
    }
private:
    template <typename T, typename... Rest, typename... Args>
    auto check (rank<0, T, Rest...>, Args... args) const
            -> std::enable_if_t<has_argument_type<T, Args...>(), decltype(T(args...))> {
        return T(args...);
    }

    template <typename T, typename... Rest, typename... Args>
    auto check (rank<0, T, Rest...>, Args... args) const
            -> std::enable_if_t<!has_argument_type<T, Args...>(), decltype(check(rank<0, Rest...>{}, args...))> {
        return check(rank<0, Rest...>{}, args...);
    }

    template <typename T, typename... Args>
    auto check (rank<0,T>, Args... args) const
            -> std::enable_if_t<!has_argument_type<T, Args...>(), decltype(check(rank<1, Ts...>{}, args...))> {
        return check(rank<1, Ts...>{}, args...);  // Since rank<0,T> derives immediately from rank<1, Ts...>.
    }

    template <std::size_t N, typename T, typename... Rest, typename... Args>
    auto check (rank<N, T, Rest...>, Args... args) const
            -> std::enable_if_t<has_argument_type_n<T, N-1, Args...>(), decltype(T(args...))> {
        return T(args...);
    }

    template <std::size_t N, typename T, typename... Rest, typename... Args>
    auto check (rank<N, T, Rest...>, Args... args) const
            -> std::enable_if_t<!has_argument_type_n<T, N-1, Args...>(), decltype(check(rank<N, Rest...>{}, args...))> {
        return check(rank<N, Rest...>{}, args...);
    }

//  I want to use the following instead of what's below it.
//  template <std::size_t N, typename T, typename... Args>
//  auto check (rank<N,T>, Args... args) const
//          -> std::enable_if_t<!has_argument_type_n<T, N-1, Args...>(), decltype(check(rank<N+1, Ts...>{}, args...))> {
//      return check(rank<N+1, Ts...>{}, args...);  // Since rank<N,T> derives immediately from rank<N+1, Ts...>.
//  }
//
//  template <typename T, typename... Args>
//  auto check (rank<10, T>, Args... args) const { std::cout << "Nothing found.\n"; }

    template <typename T, typename... Args>
    auto check (rank<1,T>, Args... args) const
            -> std::enable_if_t<!has_argument_type_n<T, 0, Args...>(), decltype(check(rank<2, Ts...>{}, args...))> {
        return check(rank<2, Ts...>{}, args...);  // Since rank<1,T> derives immediately from rank<2, Ts...>.
    }

    template <typename T, typename... Args>
    auto check (rank<2,T>, Args... args) const
            -> std::enable_if_t<!has_argument_type_n<T, 1, Args...>(), decltype(check(rank<3, Ts...>{}, args...))> {
        return check(rank<3, Ts...>{}, args...);  // Since rank<2,T> derives immediately from rank<3, Ts...>.
    }
    // etc... until rank<9>.
};

// Testing
struct Object {
    template <std::size_t, typename = void> struct ArgumentType;
    template <typename T> struct ArgumentType<0,T> { using type = std::tuple<int, bool, char, double>; };
    template <typename T> struct ArgumentType<1,T> { using type = std::tuple<bool, char, double>; };
    template <std::size_t N> using argument_type = typename ArgumentType<N>::type;

    Object (int, bool, char, double) { print(); }
    Object (bool, char, double) { print(); }
    void print() const { std::cout << "Object\n"; }
};

struct Thing {
    template <std::size_t, typename = void> struct ArgumentType;
    template <typename T> struct ArgumentType<0,T> { using type = std::tuple<int, int, char>; };
    template <typename T> struct ArgumentType<1,T> { using type = std::tuple<int, char>; };
    template <typename T> struct ArgumentType<2,T> { using type = std::tuple<char>; };
    template <std::size_t N> using argument_type = typename ArgumentType<N>::type;

    Thing (int, int, char) { print(); }
    Thing (int, char) { print(); }
    Thing (char) { print(); }
    void print() const { std::cout << "Thing\n"; }
};

struct Blob {
    using argument_type = std::tuple<int, double>;

    Blob (int, double) { print(); }
    void print() const { std::cout << "Blob\n"; }
};

struct Widget {
    using argument_type = std::tuple<int>;
    Widget (double, double, int, double) { print(); }
    Widget (int) { print(); }
    void print() const { std::cout << "Widget\n"; }
};

int main() {
    Factory<Blob, Object, Thing, Widget>().create(4,3.5);  // Blob
    Factory<Object, Blob, Widget, Thing>().create(2);  // Widget
    Factory<Object, Thing, Blob, Widget>().create(5);  // Widget
    Factory<Blob, Object, Thing, Widget>().create(4,true,'a',7.5);  // Object
    Factory<Blob, Thing, Object, Widget>().create(true,'a',7.5);  // Object
    Factory<Blob, Object, Thing, Widget>().create('a');  // Thing
}

Sé que hay otras formas de lograr esto, pero estoy tratando de entender mejor la clasificación de secuencia, y me gustaría saber por qué no puedo usar la sección comentada. Cómo evitar el código repetitivo que necesito poner (pararank<9>, o incluso un rango más alto) que actualmente está haciendo que este código funcione? Gracias por su paciencia.

Nota: De hecho, NO debo ingresar la parte repetitiva del código manualmente como lo tengo actualmente. Porque el mayor valor de N pararank<N, Ts...> utilizado en elcheck las sobrecargas se determinarán durante el tiempo de compilación como el valor N más alto de modo que unargument_type<N> tipo de miembro existe entre todos losTs.... Por lo tanto, TENGO que usar la parte genérica que comenté, y elrank<10,T> Estoy usando tendrá que reemplazar el 10 por ese valor N específico. Por lo tanto, esto no es solo una cuestión de conveniencia. Tengo que resolver este problema para continuar desarrollando el programa.

Editar: Aquí hay un ejemplo más mínimo, que muestra el mismo problema:

#include <iostream>
#include <type_traits>
#include <tuple>

template <typename T>
constexpr auto has_argument_type_impl(int)
    -> decltype(typename T::argument_type{}, std::true_type{});

template <typename T>
constexpr auto has_argument_type_impl(long) -> std::false_type;

template <typename T>
constexpr bool has_argument_type() { return decltype(has_argument_type_impl<T>(0))::value; }

template <typename... Ts>
class Factory {
    template <std::size_t, typename...> struct rank;

    template <std::size_t N, typename First, typename... Rest>
    struct rank<N, First, Rest...> : rank<N, Rest...> {};

    template <std::size_t N, typename T> struct rank<N,T> : rank<N+1, Ts...> {};

    template <typename T> struct rank<10, T> {};  // Need to end the instantiations somewhere.
public:
    template <typename... Args>
    decltype(auto) create (Args... args) const {
        return check(rank<0, Ts...>{}, args...);
    }
private:
    template <std::size_t N, typename T, typename... Rest, typename... Args>
    auto check (rank<N, T, Rest...>, Args... args) const
            -> std::enable_if_t<has_argument_type<T>(), decltype(T(args...))> {
        return T(args...);
    }

    template <std::size_t N, typename T, typename... Rest, typename... Args>
    auto check (rank<N, T, Rest...>, Args... args) const
            -> std::enable_if_t<!has_argument_type<T>(), decltype(check(rank<N, Rest...>{}, args...))> {
        return check(rank<N, Rest...>{}, args...);
    }

    template <typename T, typename... Args>
    auto check (rank<0,T>, Args... args) const
            -> std::enable_if_t<!has_argument_type<T>(), decltype(check(rank<1, Ts...>{}, args...))> {
        return check(rank<1, Ts...>{}, args...);  // Since rank<0,T> derives immediately from rank<1, Ts...>.
    }

//  I want to use the following instead of what's below it.
//  template <std::size_t N, typename T, typename... Args>
//  auto check (rank<N,T>, Args... args) const
//          -> std::enable_if_t<!has_argument_type<T>(), decltype(check(rank<N+1, Ts...>{}, args...))> {
//      return check(rank<N+1, Ts...>{}, args...);  // Since rank<N,T> derives immediately from rank<N+1, Ts...>.
//  }
//
//  template <typename T, typename... Args>
//  auto check (rank<10, T>, Args... args) const { std::cout << "Nothing found.\n"; }

    template <typename T, typename... Args>
    auto check (rank<1,T>, Args... args) const
            -> std::enable_if_t<!has_argument_type<T>(), decltype(check(rank<2, Ts...>{}, args...))> {
        return check(rank<2, Ts...>{}, args...);  // Since rank<1,T> derives immediately from rank<2, Ts...>.
    }

    template <typename T, typename... Args>
    auto check (rank<2,T>, Args... args) const
            -> std::enable_if_t<!has_argument_type<T>(), decltype(check(rank<3, Ts...>{}, args...))> {
        return check(rank<3, Ts...>{}, args...);  // Since rank<2,T> derives immediately from rank<3, Ts...>.
    }
    // etc... until rank<9>.
};

// Testing
struct Object {};
struct Thing {};

struct Blob {
    using argument_type = std::tuple<int, double>;
    Blob (int, double) { std::cout << "Blob\n"; }
};

int main() {
    Factory<Object, Thing, Blob>().create(4,3.5);  // Blob
}

Respuestas a la pregunta(1)

Su respuesta a la pregunta