Явная специализация функции-члена с вариативными аргументами шаблона

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

У меня есть функция-член "emit", которая создаст объект Event из предоставленных параметров вариационного шаблона и перенаправит событие. Тем не менее, я хотел бы предоставить специализированную функцию «выдачи», которая будет определять, является ли предоставленный аргумент предварительно сконструированным объектом Event, и в этом случае я мог бы переслать событие, не делая лишней копии.

Моя первая попытка...

template <typename T_Event>
class EventHandler
{
public:

  template <typename... T_Args>
  void emit(T_Args&&... args)
  {
    printf("variadic\n");
    deliver(T_Event {std::forward<T_Args>(args)...});
  }

  void emit(const T_Event& event)
  {
    printf("reference\n");
    deliver(event);
  }

  ...
};

Я использовал следующую логику, чтобы опробовать обе функции emit, но оказалось, что функция шаблона с переменным числом аргументов всегда имеет приоритет над ссылочной функцией const.

struct Event { int x; };

EventHandler<Event> handler;
Event event {1};
handler.emit(event);
handler.emit(2);

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

  template <typename... T_Args>
  void emit(T_Args&&... args)
  {
    printf("variadic\n");
    T_Event event {std::forward<T_Args>(args)...};
    deliver(event);
  }

  template <typename... T_Args, typename = std::enable_if<std::is_same<const T_Event&, T_Args...>::value>>
  void emit(T_Args&&... args)
  {
    printf("reference\n");
    deliver(std::forward<T_Args>(args)...);
  }

Это решение работало именно так, как мне нужно, когда я компилирую с помощью GCC, но если я компилирую с помощью CLANG, я получаю следующее сообщение об ошибке:

  call to member function 'emit' is ambiguous
handler.emit(event);

  candidate function [with T_Args = <EventB &>]
void emit(T_Args&&... args)

  candidate function [with T_Args = <EventB &>, $1 =
  std::__1::enable_if<false, void>]
void emit(T_Args&&... args)

Я предполагаю, что я близок к правильному решению, может ли кто-нибудь объяснить, что я делаю неправильно?

Правильный способ - иметь const T_Event& и T_Event&&.

Passer By 30.05.2019 12:48

Проблема заключается в том, что T_Event&& по-прежнему необходимо создавать до вызова функции emit, а не предоставлять исходные аргументы конструктора Event для функции emit.

Unl1ght 30.05.2019 13:03

invoke означает std::invoke?

L. F. 30.05.2019 13:09

Нет, это другая функция внутри класса. Я переименую его в примере, чтобы избежать путаницы.

Unl1ght 30.05.2019 13:22
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
4
412
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

Это делается путем удаления ссылок и квалификаторов const из каждого типа в пакете параметров, сборки из них кортежа и использования std::is_same, как и в вашей попытке, для сравнения полученного типа с std::tuple<T_Event>, а затем неудачного разрешения перегрузки в этом случай, отрицая черты типа (это не std::tuple<T_Event>.

#include <type_traits>
#include <tuple>
#include <iostream>

template <typename T_Event>
class EventHandler
{
public:

    template <typename... T_Args,
          typename=std::enable_if_t
          <std::negation<std::is_same
                 <std::tuple<std::remove_const_t
                         <std::remove_reference_t
                          <T_Args>>...>,
                          std::tuple<T_Event>>
                              >::value>>
    void emit(T_Args&&... args)
    {
        std::cout << "Variadic" << std::endl;
    }

    void emit(const T_Event& event)
    {
        std::cout << "Reference" << std::endl;
    }
};

int main()
{
    EventHandler<const char *> foo;
    const char *bar = "foobar";

    foo.emit(4);
    foo.emit(4, "bar");
    foo.emit(bar);
    return 0;
}

Протестировано с помощью g++ с параметром -std=c++17. Это должно быть выполнимо с C++11 и C++14 путем повторной реализации некоторых отсутствующих признаков типа. Конечный результат:

$ g++ -o t -std=c++17 t.C
$ ./t
Variadic
Variadic
Reference

Большое спасибо, сэр!

Unl1ght 31.05.2019 01:07

Другие вопросы по теме

Похожие вопросы

C++/GoogleTest - Как запустить программу установки для каждого теста в каталоге ссылок
Передача произвольного количества параметров с использованием вектора в С++
Об использовании пространства имен std в C++
Почему библиотечная функция С++ std::min_element принимает функтор, который принимает объект функции типа возврата bool, а не int, как в C?
Вопиющая ошибка с плавающей запятой в программе C++
Размер дочернего класса с алмазной структурой наследования кажется странным
Дублирование C++ с помощью VS. Отслеживайте переменные изменения
Linux: может ли переименование каталога быть частично выполнено из-за сбоя питания/временного механического сбоя на сервере/диске?
Умные указатели нельзя использовать как необработанные указатели автоматически?
Есть ли способ установить определенное количество максимальных пробелов для символа TAB (обратная косая черта t) «\ t» в С++?