Реализация функции с enable_if вне определения класса
В общем, сейчас у меня есть очень базовый родовой класс, в настоящее время тестирующий заголовок type_traits. В настоящее время я пытаюсь заставить функцию работать с определенными типами, то есть арифметическими, на данный момент.
#include <type_traits>
template <typename T> class Test {
public:
template <typename U = T>
typename std::enable_if<std::is_arithmetic<U>::value>::type print();
};
Функция работает отлично и только для арифметических типов.
Но мне нравится держать свои классы в чистоте и иметь только прототипы, в то время как реализации функций находятся за пределами класса.
Со стандартными шаблонами т.е.
void test();
template <typename T> void Test<T>::test() {}
Это просто, и я знаю, как это сделать, но я понятия не имею, как объявить реализацию вне класса с помощью "std::enable_if
"и каждая попытка, которую я сделал, во время компиляции говорит, что этот прототип не соответствует ни одному в классе.
Мне удалось найти подобный вопрос здесь, но класс там стандартный, а не общий.
PS. Я использую MinGW-w64 с -std= C++17
6 ответов
Вам нужен один набор параметров шаблона для шаблона класса и один отдельный набор параметров шаблона для шаблона функции-члена. Вам необходимо повторить весь сложный тип возвращаемого значения, поскольку он является частью сигнатуры шаблона функции. И обратите внимание, вы не можете повторить аргумент по умолчанию =T
или компилятор решит, что вы пытаетесь определить его дважды (без проверки, является ли новое определение идентичным).
template <typename T> template <typename U>
typename std::enable_if<std::is_arithmetic<U>::value>::type
Test<T>::print()
{
// Implementation here.
}
Кстати, вы используете "длинный путь" написания типа, как это было необходимо в C++11. Но C++14 ввел std::enable_if_t
ярлык, и C++17 ввел std::is_arithmetic_v
ярлык. Так что если вы используете C++17, вы также можете написать
typename std::enable_if<std::is_arithmetic<U>::value>::type
как раз
std::enable_if_t<std::is_arithmetic_v<U>>
Если вы положите enable_if
в параметре шаблона по умолчанию, который в любом случае лучше, определение вне класса становится немного проще:
template<typename T>
struct Test
{
template <typename S = T
, typename = typename std::enable_if<std::is_arithmetic<S>::value>::type >
void print();
};
template<typename T>
template<typename S, typename>
void Test<T>::print()
{
//some code
}
Вы можете попробовать с
template <typename T>
template <typename U>
std::enable_if_t<std::is_arithmetic<U>::value> Test<T>::print()
{ /* do something */ }
Ниже приводится полный рабочий пример
#include <iostream>
#include <type_traits>
template <typename T> class Test
{
public:
template <typename U = T>
std::enable_if_t<std::is_arithmetic<U>::value> print();
};
template <typename T>
template <typename U>
std::enable_if_t<std::is_arithmetic<U>::value> Test<T>::print()
{ std::cout << "test!" << std::endl; }
int main ()
{
Test<int> ti;
Test<void> tv;
ti.print(); // compile
//tv.print(); // compilation error
}
Оффтопик 1
Обратите внимание, что ваше решение может быть взломано таким образом
Test<void>{}.print<int>();
Чтобы избежать этой проблемы, вы можете навязать T
равно U
,
template <typename T> class Test
{
public:
template <typename U = T>
std::enable_if_t< std::is_arithmetic<U>::value
&& std::is_same<T, U>::value> print()
{ }
};
Оффтопик 2
Как видите, вы должны повторить часть SFINAE (std::enable_if_t
, std::is_arithmetic
а также std::is_same
).
Принимая во внимание, что вы должны повторить реализацию в заголовке, я не думаю (IMHO), что писать реализацию шаблонных классов вне тела класса - это отличная идея.
Поскольку вы не опубликовали то, что пытались сделать, я не могу сказать вам, где вы ошиблись. Но именно так вы бы реализовали функцию-член вне определения класса (хотя она все еще должна быть реализована в заголовке, так что я не думаю, что это того стоит)
template <typename T> class Test {
public:
template <typename U = T>
typename std::enable_if<std::is_arithmetic<U>::value>::type print();
};
template <typename T> // class template parameter
template <typename U> // function template parameter
inline typename std::enable_if<std::is_arithmetic<U>::value>::type Test<T>::print()
{
}
Если вам нужен дополнительный параметр шаблона U, как объяснили другие ответы, правильный синтаксис
template<typename T>
struct test
{
template<typename U>
... a_method(...);
};
template<typename T>
template<typename U>
... test<T>::a_method(...)
{
...
}
Однако в вашем особом случае, если вам нужно только проверить некоторые свойства T
типа это действительно лишнее осложнение. Введение U
тип "искусственный" и только здесь из-за SFINAE
ИМХО, это намного элегантнее и проще в использовании if constexpr
#include <iostream>
#include <type_traits>
template <typename T>
class Test
{
public:
void print();
};
template <typename T>
void Test<T>::print()
{
if constexpr (std::is_arithmetic_v<T>)
{
std::cout << "\nOk T is arithmetic";
// ... your implementation here ...
}
else
{
// throw an exception or do what ever you want,
// here a compile-time error
static_assert(!std::is_same_v<T, T>, "not implemented yet...");
}
}
main()
{
Test<int> t;
t.print();
Test<void> t2;
// t2.print(); <- will generate a compile time error
}
template<typename T>
struct test
{
template<typename U = T>
typename std::enable_if<std::is_arithmetic<U>::value>::type print();
};
template<typename T> template<typename U>
typename std::enable_if<std::is_arithmetic<U>::value>::type test<T>::print()
{
}
void foo()
{
test<int> t;
t.print();
test<void*> u;
u.print();
}