L'exemple que vous avez donné fonctionnera avec les éléments suivants, mais il y a quelques réserves, mentionnées ci-dessous.
#include<type_traits>
#include<utility>
struct ubiq
{
template<typename T>
constexpr operator T&() const;
};
template<size_t>
ubiq make_ubiq();
struct broken_t;
template<typename T>
static constexpr bool is_broken_v = std::is_same_v<T, broken_t>;
template<typename T, size_t I0, size_t... I>
auto call(std::index_sequence<I0, I...>)
-> decltype(std::declval<T>()(make_ubiq<I0>(), make_ubiq<I>()...));
template<typename T>
auto call(std::index_sequence<>) -> decltype(std::declval<T>()());
template<typename T, size_t... I>
auto call(std::index_sequence<I...>) -> broken_t;
template<typename T, size_t N>
using call_t = decltype(call<T>(std::make_index_sequence<N>{}));
template<typename Void, typename...>
struct collapse
{
using type = broken_t;
};
template<typename T>
struct collapse<std::enable_if_t<!is_broken_v<T>>, T>
{
using type = T;
};
template<typename T, typename... Ts>
struct collapse<std::enable_if_t<!is_broken_v<T>>, T, broken_t, Ts...> :
collapse<void, T, Ts...> {};
template<typename... Ts>
struct collapse<void, broken_t, Ts...> :
collapse<void, Ts...> {};
template<typename T, typename... Ts>
struct collapse<std::enable_if_t<!is_broken_v<T>>, T, T, Ts...> :
collapse<void, T, Ts...> {};
template<typename... Ts>
using collapse_t = typename collapse<void, Ts...>::type;
template<typename, typename>
struct unique_call;
template<typename T, size_t... Ns>
struct unique_call<T, std::index_sequence<Ns...>>
{
using type = collapse_t<call_t<T, Ns>...>;
};
template<typename T, size_t N = 10>
using unique_call_t = typename unique_call<T, std::make_index_sequence<N>>::type;
Les affirmations suivantes passent
auto f = [](auto x, auto y, int z) -> int {return 42;};
auto g = [](double x, auto y) -> float {return 4.2;};
static_assert(std::is_same_v<int, unique_call_t<decltype(f)>>);
static_assert(std::is_same_v<float, unique_call_t<decltype(g)>>);
En direct
La façon dont cela fonctionne est de "scanner" un type et de voir si un nombre quelconque d'arguments peut être utilisé pour l'appeler. La limite supérieure des arguments doit être spécifiée à l'avance, mais en réalité, si quelqu'un me donne quelque chose avec plus de dix paramètres, je vais juste prétendre que cela n'existe pas de toute façon :D
L'ensemble des types de retour possibles est ensuite vérifié, s'il y a différents types, ou s'il n'y en a aucun, le type résultant sera broken_t
.
struct S
{
int operator()(int);
float operator()(float);
};
struct U {};
static_assert(std::is_same_v<broken_t, unique_call_t<S>>); // passes
static_assert(std::is_same_v<broken_t, unique_call_t<U>>); // passes
Mises en garde
Cette méthode ne permet pas de différencier des éléments inexistants operator()
et un autre qui est surchargé pour le même nombre d'arguments. Le type suivant sera perçu comme ayant seulement int operator()()
.
struct S
{
int operator()();
int operator()(int);
float operator()(float);
};
static_assert(std::is_same_v<int, unique_call_t<S>>); // passes!??
Je n'ai pas encore trouvé de méthode qui peut faites-le.
Un autre problème concerne les modèles
template<typename T, std::enable_if_t<std::is_integral<T>>* = nullptr>
int operator()(T);
Puisque nous avons triché et créé le type ubiq
et que vous l'avez utilisé comme argument de remplacement, il ne sera pas compatible avec les modèles, T
ne sera pas une intégrale dans ce cas.