2 votes

obtenir le type de retour unique d'une fonction modèle ou d'un lambda générique

Existe-t-il un moyen d'obtenir le type de retour d'une fonction modèle ou d'un lambda générique sans avoir à spécifier les types d'arguments ?

template<class F>
struct unique_result_of {
    using type = std::result_of_t<F( *any valid parameter combination that F can be called upon* )>;
}

auto f = [](auto x, auto y, int z) -> int {};
auto g = [](double x, auto y) -> float {};

using return_type_f = typename unique_result_of<decltype(f)>::type; // should be int
using return_type_g = typename unique_result_of<decltype(g)>::type; // should be float

Bien sûr, cela ne devrait fonctionner que si toutes les fonctions générées par le même nom de fonction de modèle ont un type de retour unique.

3voto

Passer By Points 9171

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.

2voto

Vittorio Romeo Points 2559

Malheureusement, il n'y a aucun moyen de le faire pour le moment car il n'y a aucun moyen de demander au compilateur ce que le développeur a mis après -> dans un type de retour arrière . Peut-être réflexion pourrait rendre cela possible à l'avenir.

Votre meilleure chance est de spécifier les types d'arguments ou d'utiliser un type qui est implicitement convertible en n'importe quoi d'autre pour simuler une invocation de la lambda :

struct any_type
{
    template <typename T>
    constexpr operator T() { return *this; }
};

Ce n'est pas parfait car cela ne fonctionne pas si le corps de la lambda tente d'invoquer une méthode qui any_type ne se "moque" pas.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X