Проблемы с универсальной ссылкой условного типа

Итак, у меня есть функция, которую я использую для проверки значений и создания исключения, если значение недопустимо, в противном случае возвращаю значение обратно в том виде, в котором оно было получено. Я пытаюсь обобщить эту рутину с помощью универсальных реверансов и типичных черт. Я чувствую, что близок, поскольку мой пример работает в некоторых случаях, но не во всех. Похоже, он работает только с rvalue.

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

Производит вывод

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

Если я раскомментирую последнюю строку, я получаю следующую ошибку:

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'

Похоже, что has_empty<T>::value оценивается как false. Я уверен, что мог бы сделать другую работу, чтобы заставить это работать, так что на данный момент это своего рода академический подход. Тем не менее, любая помощь будет оценена.


person Apeiron    schedule 24.02.2015    source источник


Ответы (1)


Когда вы передаете lvalue в do_check, он выводит T как HasEmpty& в вашем примере. Ссылочный тип, конечно, не имеет функции-члена с именем empty, а выражение decltype(&U::empty) имеет неправильный формат, что приводит к тому, что перегрузка static std::true_type check(int) приводит к выводу SFINAE.

Если вы замените

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

с участием

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

так что U никогда не будет ссылочным типом, ваш код работает так, как ожидалось.

Демо

person Praetorian    schedule 25.02.2015
comment
Спасибо! это работает как шарм. Я все еще не понимаю, почему, я понимаю, что тип выводится в ссылку, и это желательно, но почему ссылка не имеет функционального члена? Также спасибо за быстрый ответ. - person Apeiron; 25.02.2015
comment
@Apeiron Только у типов классов есть функции-члены, у ссылочных типов их нет. Я не знаю, как это лучше объяснить, возможно, эти сообщения об ошибках помогут вам понимать. - person Praetorian; 25.02.2015
comment
Работает ли ссылка в его контексте так же, как указатель? Я знаю, что указатель - это просто адрес памяти, но я всегда думал, что ссылки - это просто псевдонимы, и во всех смыслах и целях это то же самое, что и не ссылка. Похоже, что признаки типа для ссылки ближе к признакам указателя, чем к классу. Спасибо за объяснение, это было очень полезно. - person Apeiron; 25.02.2015
comment
Да, аналогия с указателем кажется правильной (но, конечно, не путайте указатели и ссылки в целом). Объект ссылочного типа такой же, как исходный объект, и доступ к члену, выполняемый для него, такой же, как и для исходного. Однако ссылочный тип отличается от нессылочного типа. - person Praetorian; 25.02.2015