2017-05-09 1 views
4

Je voudrais être en mesure de déterminer au moment de la compilation, étant donné un type lambda générique, si elle peut être invoquée avec un ensemble donné de types de paramètres. J'ai l'exemple suivant d'implémentation C++ 14:Existe-t-il un moyen de détecter au moment de la compilation si un lambda générique peut être appelé avec succès avec un ensemble donné de types de paramètres?

#include <iostream> 

// helper function; this overload handles the case that the call is possible 
// use SFINAE with the extra template parameter to remove this from consideration when the 
// call is ill-formed 
template <typename Func, typename... Args, typename = decltype(std::declval<Func>()(std::declval<Args>()...))> 
auto eval(Func f, Args &&... args) { return f(args...); } 

// special type returned from `eval()` when the call can't be done 
struct invalid_call { }; 

// helper function; this overload handles the case that the call is not possible 
template <typename Func> 
invalid_call eval(Func f, ...) { return invalid_call{}; }; 

// bring in std::negation from C++17 to help create the below trait 
template<class B> 
struct negation : std::integral_constant<bool, !bool(B::value)> { }; 

// trait that determines whether `Func` can be invoked with an argument list of types `Args...` 
template <typename Func, typename... Args> 
using can_call = negation<std::is_same<decltype(eval(std::declval<Func>(), std::declval<Args>()...)), invalid_call>>; 

// arbitary type that has no `operator+` 
struct foo {}; 

int main() 
{ 
    auto func = [](auto a1, auto a2) -> decltype(a1 + a2) { return a1 + a2; }; 
    using FuncType = decltype(func); 

    std::cout << "can call with (int, int): " << can_call<FuncType, int, int>::value << std::endl; 
    std::cout << "can call with (foo, foo): " << can_call<FuncType, foo, foo>::value << std::endl; 
} 

Cet exemple fonctionne correctement tel quel. Ce que je ne suis pas LIKE est la façon lourde que le lambda doit être déclaré:

auto func = [](auto a1, auto a2) -> decltype(a1 + a2) { return a1 + a2; }; 

C'est, le type de retour de fuite doit être spécifiée parce que C++14's deduced return types don't work with SFINAE. La déduction de type de retour nécessite la substitution des types de liste d'arguments dans l'opérateur d'appel de modèle de l'appelable, et le programme est mal formé si une erreur s'y produit.

Idéalement, je serais en mesure de faire ce qui suit:

auto func = [](auto a1, auto a2) { return a1 + a2; }; 

et laisser le type de retour lui-même automatiquement; ce serait l'interface la plus intuitive à fournir à mes utilisateurs. Ceci est un exemple très simple, donc l'argument du decltype() ne semble pas mauvais, mais en pratique, le lambda pourrait être plusieurs instructions, ce qui ne fonctionnerait pas avec cette approche. Donc ma question est:

En utilisant des techniques C++ modernes (C++ 14 serait le mieux, mais je suis ouvert aux nouvelles fonctionnalités si nécessaire), est-il possible de tester au moment de la compilation si un lambda générique peut éventuellement être appelé avec une liste arbitraire de types de paramètres?

+1

On dirait que vous avez la réponse à votre question dans votre question: non, pas sans le type de retour-retour. – Barry

+0

@Barry: Je pensais que cela pourrait être le cas, mais mes connaissances modernes en C++ ne font pas autorité, donc je voulais voir s'il y avait d'autres trucs là-bas. Si je suis limité par le besoin du type de retour, cela rend cette approche beaucoup moins utile puisque le lambda ne peut pas avoir plusieurs instructions. –

+0

Étant donné que la déduction de type de retour nécessite que votre lambda déduise le même type de retour à chaque déduction, vous pouvez simplement utiliser la première déclaration de retour (ou la plus simple) comme type de retour final decltype. Ce n'est pas vraiment une solution, mais cela réduit l'espace du problème. – TBBle

Répondre

3

Bien sûr, en utilisant le C++ 98 fonction des macros

#define RETURNS(...) noexcept(noexcept(__VA_ARGS__))->decltype(__VA_ARGS__) { return __VA_ARGS__; } 

puis

auto func = [](auto a1, auto a2) RETURNS(a1+a2); 

-t-il.

Il y a une proposition 20 C++ par notre @Barry propre qui rend cette

auto func = [](auto a1, auto a2) => a1+a2; 

sans l'utilisation de macros.

En général, il n'est pas possible et il n'est pas non plus envisagé de forcer le corps d'une fonction ou d'un lambda à être compilé pour déterminer si une expression SFINAE est acceptable ou non. De telles erreurs sont supposées être dures, car cela simplifie le travail des compilateurs C++; ils n'ont pas besoin d'être capables de compiler des corps entiers de fonctions arbitraires et ensuite de revenir proprement à un état sans erreur tout en déterminant si la résolution de surcharge réussit ou non.

Dans le cas de plusieurs instructions de retour ou d'un ensemble de types complexes utilisés dans l'instruction return, vous n'avez pas de chance. Écrivez le decltype. Priez que vous ayez raison.

+0

... beurk. Avoir un upvote et n'en parlons plus: p – Quentin