SFINAE: «enable_if нельзя использовать для отключения этой декларации» - PullRequest
0 голосов
/ 29 августа 2018

Почему я не могу использовать enable_if в следующем контексте?

Я бы хотел определить, имеет ли мой шаблонный объект функцию-член notify_exit

template <typename Queue>
class MyQueue
{
   public:
    auto notify_exit() -> typename std::enable_if<
            has_member_function_notify_exit<Queue, void>::value,
            void
        >::type;

    Queue queue_a;
};

Инициализируется с:

MyQueue<std::queue<int>> queue_a;

Я продолжаю получать (лязг 6):

example.cpp:33:17: error: failed requirement 'has_member_function_notify_exit<queue<int, deque<int, allocator<int> > >, void>::value';
      'enable_if' cannot be used to disable this declaration
            has_member_function_notify_exit<Queue, void>::value,

или (г ++ 5,4):

In instantiation of 'class MyQueue<std::queue<int> >':
33:35:   required from here
22:14: error: no type named 'type' in 'struct std::enable_if<false, void>'

Я пробовал кучу разных вещей, но не могу понять, почему я не могу использовать enable_if, чтобы отключить эту функцию. Разве это не то, для чего enable_if предназначен?

Я поместил полный пример здесь ссылка cpp.sh, которая часто не срабатывает )

Я нашел аналогичные Q / As на SO, но в целом они были более сложными и пытались что-то другое.

Ответы [ 3 ]

0 голосов
/ 29 августа 2018

С C ++ 20 и концепцией вы можете использовать requires:

void notify_exit() requires has_member_function_notify_exit<Queue, void>::value;
0 голосов
/ 29 августа 2018

Когда вы создаете экземпляр MyQueue<std::queue<int>>, аргумент шаблона std::queue<int> подставляется в шаблон класса. В объявлении функции-члена, которое приводит к использованию typename std::enable_if<false, void>::type, который не существует. Это ошибка. Вы не можете объявить функцию, используя тип, который не существует.

Правильное использование enable_if должно зависеть от параметра шаблона, который выводится . Во время вывода аргумента шаблона, если замена выведенного аргумента шаблона на параметр шаблона не удалась (то есть «ошибка замены»), то вы не получите немедленной ошибки, это просто приведет к сбою дедукции. Если вычет не удался, функция не подходит для разрешения перегрузки (но любые другие перегрузки все равно будут учитываться).

Но в вашем случае аргумент шаблона не выводится при вызове функции, он уже известен, потому что он исходит из окружающего шаблона класса. Это означает, что ошибка замещения является ошибкой, поскольку объявление функции некорректно до того, как вы попытаетесь выполнить разрешение перегрузки для его вызова.

Вы можете исправить свой пример, превратив функцию в шаблон функции, чтобы он имел параметр шаблона, который должен быть выведен:

template<typename T = Queue>
  auto notify_exit() -> typename std::enable_if<
              has_member_function_notify_exit<T, void>::value,
              void
          >::type;

Здесь условие enable_if зависит от T вместо Queue, поэтому, существует или нет элемент ::type, неизвестно, пока вы не попытаетесь заменить аргумент шаблона для T. Шаблон функции имеет аргумент шаблона по умолчанию, так что если вы просто вызываете notify_exit() без списка аргументов шаблона, это эквивалентно notify_exit<Queue>(), что означает, что условие enable_if зависит от Queue, как вы изначально хотели.

Эта функция может быть использована неправильно, так как вызывающие могут вызывать ее как notify_exit<SomeOtherType>(), чтобы обмануть условие enable_if в зависимости от неправильного типа. Если вызывающие делают это, они заслуживают ошибок компиляции.

Другим способом заставить код работать, было бы иметь частичную специализацию всего шаблона класса, просто удалить функцию, когда она не нужна:

template <typename Queue,
          bool Notifiable
            = has_member_function_notify_exit<Queue, void>::value>
class MyQueue
{
  public:
    void notify_exit();

    Queue queue_a;
};

// partial specialization for queues without a notify_exit member:
template <typename Queue>
class MyQueue<Queue, false>
{
  public:
    Queue queue_a;
};

Вы можете избежать повторения всего определения класса дважды несколькими различными способами. Вы можете поднять весь общий код в базовый класс и добавить только член notify_exit() в производный класс, который зависит от него. В качестве альтернативы вы можете переместить только условную часть в базовый класс, например:

template <typename Queue,
          bool Notifiable
            = has_member_function_notify_exit<Queue, void>::value>
class MyQueueBase
{
  public:
    void notify_exit();
};

// partial specialization for queues without a notify_exit member:
template <typename Queue>
class MyQueueBase<Queue, false>
{ };

template<typename Queue>
class MyQueue : public MyQueueBase<Queue>
{
public:
  // rest of the class ...

  Queue queue_a;
};

template<typename Queue, bool Notifiable>
void MyQueueBase<Queue, Notifiable>::notify_exit()
{
  static_cast<MyQueue<Queue>*>(this)->queue_a.notify_exit();
}
0 голосов
/ 29 августа 2018

Создание шаблона вызывает создание экземпляра для всех объявлений, которые он содержит. Заявление, которое вы предоставляете, просто неправильно оформлено. Кроме того, SFINAE здесь не применяется, поскольку мы не разрешаем перегрузки, когда создается экземпляр класса.

Необходимо преобразовать элемент во что-то с действительным объявлением, а также убедиться, что проверка отложена до разрешения перегрузки. Мы можем сделать и то и другое, сделав notify_exit сам шаблон:

template<typename Q = Queue>
auto notify_exit() -> typename std::enable_if<
        has_member_function_notify_exit<Q, void>::value,
        void
    >::type;

Рабочий пример cpp.sh

...