Выбор функции-члена с использованием различных условий 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
}
};
Выше работник(), конечно, будет работать, даже если он объявлен как статический. По какой-то уважительной причине. Несколько других расширений к вышеупомянутому возможны (и просты), но давайте придерживаться ответа.