3

So I have a function I use to check values and throw an exception if the value is not valid, otherwise pass the value back as it was received. I am trying to generalize this routine with universal reverences and type-traits. I feel I am close as my example works in some cases, but not all. It appears to only work with rvalues.

#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
}

Produces the output

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

If I uncomment the last line I get the following error:

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'

Which appears to that has_empty<T>::value is evaluating to false. I'm sure I could do a different work around to get this to work, so at this point it is kind of academic. Still, any help would be appreciated.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
Apeiron
  • 694
  • 7
  • 13

1 Answers1

3

When you pass an lvalue to do_check it deduces T as HasEmpty& in your example. The reference type, of course, has no member function named empty and the expression decltype(&U::empty) is ill-formed causing the static std::true_type check(int) overload to get SFINAE'd out.

If you replace

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

with

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

so that U is never a reference type, your code works as expected.

Live demo

Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • Thank you! that works like a charm. I'm still confused as to why, I understand that the type is deduced to a Reference, and this is desired, but why wouldn't a reference have the function member? Also, thanks for the quick response. – Apeiron Feb 25 '15 at 00:51
  • 1
    @Apeiron Only class types have member functions, reference types don't have any. I don't know how to explain it better, perhaps [these error messages](http://coliru.stacked-crooked.com/a/7f64db6fae3b4084) will help you understand. – Praetorian Feb 25 '15 at 01:05
  • Does a reference int his context work like a pointer would? I know a pointer is just a memory address, but I always thought references were just aliases and for all intents and purposes was the same as the non reference. It sounds like type-traits for a reference are closer to those of a pointer than a class. Thanks for the explanation, it was very helpful. – Apeiron Feb 25 '15 at 17:07
  • Yes, the pointer analogy seems correct (but of course, don't conflate pointers and references in general). An object of reference type is the same as the original object and member access performed on it is the same as performing it on the original. However, a reference type is distinct from a non-reference type. – Praetorian Feb 25 '15 at 18:54