Выбор функции-члена с использованием различных условий enable_if

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

#include <iostream>
#include <type_traits>

template<typename T>
struct Point
{
  void MyFunction(typename std::enable_if<std::is_same<T, int>::value, T >::type* = 0)
  {
    std::cout << "T is int." << std::endl;
  }

  void MyFunction(typename std::enable_if<!std::is_same<T, int>::value, float >::type* = 0)
  {
    std::cout << "T is not int." << std::endl;
  }
};

int main()
{
  Point<int> intPoint;
  intPoint.MyFunction();

  Point<float> floatPoint;
  floatPoint.MyFunction();
}

который, как я думал, говорит "использовать первую MyFunction, если T является int, и использовать вторую MyFunction, если T не является int, но я получаю ошибки компилятора, говорящие" error: no type named "type in 'struct std:: enable_if" Может ли кто-нибудь указать, что я здесь делаю неправильно?

Ответ 1

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

В вашем примере не существует подстановки при создании экземпляров функций-членов, поскольку аргумент шаблона T уже известен в то время. Самый простой способ добиться того, что вы пытаетесь, - создать аргумент фиктивного шаблона, который по умолчанию равен T, и использовать его для выполнения SFINAE.

template<typename T>
struct Point
{
  template<typename U = T>
  typename std::enable_if<std::is_same<U, int>::value>::type
    MyFunction()
  {
    std::cout << "T is int." << std::endl;
  }

  template<typename U = T>
  typename std::enable_if<std::is_same<U, float>::value>::type
    MyFunction()
  {
    std::cout << "T is not int." << std::endl;
  }
};

Edit:

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

template<typename T>
struct Point
{
  template<typename... Dummy, typename U = T>
  typename std::enable_if<std::is_same<U, int>::value>::type
    MyFunction()
  {
    static_assert(sizeof...(Dummy)==0, "Do not specify template arguments!");
    std::cout << "T is int." << std::endl;
  }

  template<typename... Dummy, typename U = T>
  typename std::enable_if<std::is_same<U, float>::value>::type
    MyFunction()
  {
    static_assert(sizeof...(Dummy)==0, "Do not specify template arguments!");
    std::cout << "T is not int." << std::endl;
  }
};

Ответ 2

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

template<typename T>
struct Point
{

  void MyFunction()
  {
     worker(static_cast<T*>(nullptr)); //pass null argument of type T*
  }

private:

  void worker(int*)
  {
    std::cout << "T is int." << std::endl;
  }

  template<typename U>
  void worker(U*)
  {
    std::cout << "T is not int." << std::endl;
  }
};

Когда T равен int, будет вызвана первая функция worker, потому что static_cast<T*>(0) оказывается типа int*. Во всех остальных случаях будет вызываться шаблонная версия работника.

Ответ 3

enable_if работает только для выводимых аргументов шаблона функции или для специализированных аргументов шаблона класса. То, что вы делаете, не работает, потому что очевидно, что с фиксированным T = int второе объявление просто ошибочно.

Вот как это можно сделать:

template <typename T>
void MyFreeFunction(Point<T> const & p,
                    typename std::enable_if<std::is_same<T, int>::value>::type * = nullptr)
{
    std::cout << "T is int" << std::endl;
}

// etc.

int main()
{
    Point<int> ip;
    MyFreeFunction(ip);
}

Альтернативой может быть специализация Point для различных типов T или помещение вышеуказанной свободной функции в оболочку шаблона вложенного элемента (что, вероятно, является более "правильным" решением).

Ответ 4

Основываясь на предложении Praetorian (но без изменения типа возврата функции), это работает:

#include <iostream>
#include <type_traits>

template<typename T>
struct Point
{
  template<typename U = T>
  void MyFunction(typename std::enable_if<std::is_same<U, int>::value, U >::type* = 0)
  {
    std::cout << "T is int." << std::endl;
  }

  template<typename U = T>
  void MyFunction(typename std::enable_if<!std::is_same<U, int>::value, float >::type* = 0)
  {
    std::cout << "T is not int." << std::endl;
  }
};

int main()
{
  Point<int> intPoint;
  intPoint.MyFunction();

  Point<float> floatPoint;
  floatPoint.MyFunction();
}

Ответ 5

Точечный шаблон, приведенный ниже, можно создать только с int или float в качестве аргумента шаблона T.

Чтобы ответить на вопрос: здесь worker() вызывается точно в зависимости от параметра шаблона вызова метода(), но все же вы управляете типами.

    template<typename T>
    struct Point
    {
        static_assert (
              std::is_same<T, int>()  ||
              std::is_same<T, float>()
            );

        template<typename U>
        void method(U x_, U y_)
        {
            if constexpr (std::is_same<T, U>()) {
                worker(x_, y_);
                return;
            }
            // else 
            worker(
                static_cast<T>(x_),
                static_cast<T>(y_)
            );
            return ;
        }


    private:

        mutable T x{}, y{};

        void worker(T x_, T y_)
        {
            // nothing but T x, T y
        }

    };

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