enable_if в функциях-членах для void и наследования - PullRequest
0 голосов
/ 15 января 2019

Я пытаюсь понять, почему этот код не компилируется:

// test.h
struct Base
  {
  virtual ~Base{};
  virtual void execute() {}
  virtual void execute(int) {}
  virtual void execute(double) {}
  }

template<class T>
struct Test : Base
  {
    void execute(typename std::enable_if<std::is_void<T>::value, void>::type)
      {
      // Do A
      }

    void execute(typename std::enable_if<!std::is_void<T>::value, int>::type t)
      {
      // Do B
      }
  };

// main.cpp
Test<void> t; 

Я получаю сообщение об ошибке компилятора: "тип без имени".

Та же ошибка, даже если я изменяю версию кода A с помощью

std::enable_if<std::is_void<T>::value>

Цель состоит в том, чтобы создать класс, который в зависимости от параметра T создает различные функции-члены. В этом случае 2, но мне было бы интересно также больше.

[Изменить] Я добавил часть наследования, о которой говорил в комментариях.

Ответы [ 4 ]

0 голосов
/ 15 января 2019

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

Если вы используете c ++ 17, это может быть просто благодаря if constexpr. В c ++ 11 альтернативой является использование тега dispatch:

template<class T>
struct Test : Base
  {
    void execute()
      {
      void do_execute(std::integral_constant<bool,std::is_void<T>::value>{});
      }

    void execute(int t)
      {
      void do_execute(std::integral_constant<bool,!std::is_void<T>::value>{}, t);
      }
  private:
  void do_execute(std::integral_constant<bool,true>){
       /*implementation*/
       }
  void do_execute(std::integral_constant<bool,false>){
       Base::execute();//Call directly the base function execute.
                       //Such call does not involve the devirtualization
                       //process.
       }
  void do_execute(std::integral_constant<bool,true>,int t){
       /*implementation*/
       }
  void do_execute(std::integral_constant<bool,false>,int t){
       Base::execute(t);//Call directly the base function execute.
                        //Such call does not involve the devirtualization
                        //process.
       }
  };

С C ++ 17 if constexpr это может выглядеть более элегантно, чем решение CRTP:

template<class T>
struct Test : Base
  {
    void execute(){
      if constexpr (is_void_v<T>){
         Base::execute();
         }
      else{
        /* implementation */
        }
      }

    void execute(int t){
      if constexpr (!is_void_v<T>){
         Base::execute(t);
         }
      else{
        /* implementation */
        }
      }
  };
0 голосов
/ 15 января 2019

Когда вы создали экземпляр Test<void>, вы также создали объявления всех его функций-членов. Это просто базовая реализация. Какие заявления это дает? Примерно так:

void execute(void);
void execute(<ill-formed> t);

Если вы ожидали, что SFINAE бесшумно устранит неправильно сформированную перегрузку, вам следует помнить, что буква S обозначает «замену». Подстановка аргументов шаблона в параметры (члена) шаблона функции . Ни execute не является шаблоном функции-члена. Обе они являются регулярными функциями-членами специализации шаблона.

Вы можете исправить это несколькими способами. Одним из способов может быть создание этих двух шаблонов, правильное выполнение SFINAE и разрешение перегрузки. @ YSC уже показывает вам, как.

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

template<typename T>
struct TestBase {
  void execute(T t) { }
};

template<>
struct TestBase<void> {
  void execute() { }
};

template<class T>
struct Test : private TestBase<T> {
  using TestBase<T>::execute;
};

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


Для решения вашей правки. Я думаю, что второй подход на самом деле лучше соответствует вашим потребностям.

template<typename T>
struct TestBase : Base {
  void execute(T t) override { }
};

template<>
struct TestBase<void> : Base {
  void execute() override { }
};

TestBase - средний человек, который выполняет то, за чем вы, похоже, стремитесь.

0 голосов
/ 15 января 2019

Вы можете инкапсулировать различные перегрузки execute в набор связанных вспомогательных классов, что-то вроде этого:

template <class T>
struct TestHelper : Base
{
  void execute(int) override {}
};

template <>
struct TestHelper<void> : Base
{
  void execute() override {}
};


template <class T>
struct Test : TestHelper<T>
{
  // Other Test stuff here
};

Если реализации execute на самом деле зависят от «Другие тестовые материалы», которые должны быть разделены между ними, вы также можете использовать CRTP :

template <class T, class Self>
struct TestHelper : Base
{
  void execute(int) override
  {
    Self& self = static_cast<Self&>(*this);
    // Now access all members through `self.` instead of directly
  }
};

template <class Self>
struct TestHelper<void, self> : Base
{
  void execute() override
  {
    Self& self = static_cast<Self&>(*this);
    // Now access all members through `self.` instead of directly
  }
};

template <class T>
struct Test : TestHelper<T, Test>
{
  // Other Test stuff here
};
0 голосов
/ 15 января 2019

Примечание: этот ответ полезен для предыдущего редактирования вопроса. Недавнее редактирование коренным образом изменило вопрос, и этот ответ больше не является адекватным.

Поскольку execute не является функцией шаблона, не может быть никакого вызова SFINAE. Действительно, всякий раз, когда создается экземпляр Test<void>, обе версии execute имеют место, что приводит к ошибке, которая не является ошибкой вывода шаблона.

Вам нужен шаблон функции (пусть вызовет параметр шаблона U), чтобы воспользоваться SFINAE; и поскольку вам необходимо использовать тот же аргумент шаблона типа, что и Test (T), вы можете указать аргумент по умолчанию U = T):

Решение

template<class T>
struct Test
{
    template<class U = T>
    std::enable_if_t<std::is_void_v<U>> execute()
    { std::cout << "is_void\n"; }

    template<class U = T>
    std::enable_if_t<!std::is_void_v<U>> execute()
    { std::cout << "!is_void\n"; }
};

Демонстрационная версия

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...