Реализация функции с 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();
}
Другие вопросы по тегам