3

J'ai donc une fonction que j'utilise pour vérifier les valeurs et lever une exception si la valeur n'est pas valide, sinon renvoyer la valeur telle qu'elle a été reçue. J'essaie de généraliser cette routine avec des révérences universelles et des traits de type. Je sens que je suis proche car mon exemple fonctionne dans certains cas, mais pas tous. Il semble ne fonctionner qu'avec des valeurs.Problèmes avec le type-caractère conditionnel Référence universelle

#include <iostream> 
#include <utility> 
#include <type_traits> 
#include <string> 
#include <vector> 

using namespace std; 

struct error_type{}; 

template <class T> 
class has_empty 
{ 
    template<class U, class = typename std::enable_if<std::is_member_pointer<decltype(&U::empty)>::value>::type> 
     static std::true_type check(int); 
    template <class> 
     static std::false_type check(...); 
public: 
    static constexpr bool value = decltype(check<T>(0))::value; 
}; 

template <bool invalid> 
struct valid {}; 


template <> 
struct valid<false> 
{ 
    template<typename U, typename E> 
    static inline U&& check(U&& toCheck, const E& toThrow) 
    { 
     if (!toCheck) 
     std::cout << "NoEmpty Throw" << '\n'; 
     else 
     std::cout << "NoEmpty valid" << '\n'; 
     return std::forward<U>(toCheck); 
    } 
}; 

template <> 
struct valid<true> 
{ 
    template<typename U, typename E> 
    static inline U&& check(U&& toCheck, const E& toThrow) 
    { 
     if (toCheck.empty()) 
     std::cout << "HasEmpty Throw" << '\n'; 
     else 
     std::cout << "HasEmpty valid" << '\n'; 
     return std::forward<U>(toCheck); 
    } 
}; 

template<typename T 
    , typename E 
    , typename = typename std::enable_if<std::is_base_of<error_type, E>::value>::type> 
    inline T&& do_check(T&& toCheck, const E& toThrow) 
{ 
    return valid<has_empty<T>::value>::check(std::forward<T>(toCheck), toThrow); 
} 

struct HasEmpty 
{ 
    bool empty() {return false;} 
}; 

struct NoEmpty 
{ 
}; 


int main() 
{ 
    error_type e; 

    cout << has_empty<std::wstring>::value << '\n'; 
    cout << has_empty<std::vector<std::wstring>>::value << '\n'; 
    cout << has_empty<int>::value << '\n'; 
    cout << has_empty<HasEmpty>::value << '\n'; 
    cout << has_empty<NoEmpty>::value << '\n'; 

    do_check(true, e); 
    do_check(false, e); 

    do_check(std::string("45"), e); 
    do_check(HasEmpty(), e); 
    do_check(std::vector<bool>(), e); 
    HasEmpty he; 
    do_check(std::move(he), e); 
    //do_check(he, e); // does not work, has_empty<T>::value is 0 
} 

produit la sortie

1 
1 
0 
1 
0 
NoEmpty valid 
NoEmpty Throw 
HasEmpty valid 
HasEmpty valid 
HasEmpty Throw 
HasEmpty valid 

Si je décommenter la dernière ligne, je reçois l'erreur suivante:

prog.cpp: In instantiation of 'static T&& valid<false, T, E>::check(T&&, const E&) [with T = HasEmpty&; E = error_type]': 
prog.cpp:56:84: required from 'T&& do_check(T&&, const E&) [with T = HasEmpty&; E = error_type; <template-parameter-1-3> = void]' 
prog.cpp:87:19: required from here 
prog.cpp:30:11: error: no match for 'operator!' (operand type is 'HasEmpty') 
     if (!toCheck) 
     ^
prog.cpp:30:11: note: candidate is: 
prog.cpp:30:11: note: operator!(bool) <built-in> 
prog.cpp:30:11: note: no known conversion for argument 1 from 'HasEmpty' to 'bool' 

qui semble que has_empty<T>::value évalue à false. Je suis sûr que je pourrais faire un travail différent pour que cela fonctionne, donc à ce stade c'est un peu académique. Cependant, toute aide serait appréciée.

Répondre

3

Lorsque vous transmettez une valeur lvalue à do_checkdeduces T as HasEmpty& dans votre exemple. Le type de référence, bien sûr, n'a pas de fonction membre nommée empty et l'expression decltype(&U::empty) est mal formée, entraînant la surcharge static std::true_type check(int) pour sortir de SFINAE.

Si vous remplacez

static constexpr bool value = decltype(check<T>(0))::value; 

avec

static constexpr bool value = decltype(check<typename std::remove_reference<T>::type>(0))::value; 

de sorte que U est jamais un type de référence, votre code fonctionne comme prévu.

Live demo

+0

Merci! ça fonctionne comme un charme. Je suis encore confus quant à pourquoi, je comprends que le type est déduit à une référence, et cela est souhaité, mais pourquoi une référence n'aurait pas la fonction membre? Aussi, merci pour la réponse rapide. – Apeiron

+1

@Apeiron Seuls les types de classe ont des fonctions membres, les types de références n'en ont pas. Je ne sais pas comment l'expliquer mieux, peut-être [ces messages d'erreur] (http://coliru.stacked-crooked.com/a/7f64db6fae3b4084) vous aidera à comprendre. – Praetorian

+0

Est-ce qu'une référence dans son contexte fonctionne comme un pointeur? Je sais qu'un pointeur est juste une adresse de mémoire, mais j'ai toujours pensé que les références n'étaient que des alias et que, à toutes fins utiles, c'était la même chose que la non-référence. Cela ressemble à des traits de type pour une référence qui sont plus proches de ceux d'un pointeur que d'une classe. Merci pour l'explication, c'était très utile. – Apeiron