Подтвердить что ты не робот

Специализированный одиночный метод в большом классе шаблона

В С++, если вы хотите частично специализировать один метод в классе шаблона, вам нужно специализировать весь класс (как указано, например, в Специализация шаблона одного метода из шаблонного класса с несколькими параметрами шаблона)

Это, однако, становится утомительным в более крупных классах шаблонов с несколькими параметрами шаблона, когда каждый из них влияет на одну функцию. С параметрами N вам нужно специализировать класс 2 ^ N раз!

Однако, с С++ 11, я думаю, что может быть более элегантное решение, но я не уверен, как подойти к нему. Возможно, как-то с enable_if? Любые идеи?

4b9b3361

Ответ 1

В дополнение к решению на основе наследования, предложенному Torsten, вы можете использовать std::enable_if и параметры шаблона по умолчанию для включения/отключения определенных специализация функции.

Например:

template<typename T>
struct comparer
{
    template<typename U = T ,
    typename std::enable_if<std::is_floating_point<U>::value>::type* = nullptr>
    bool operator()( U lhs , U rhs )
    {
        return /* floating-point precision aware comparison */;
    }

    template<typename U = T ,
    typename std::enable_if<!std::is_floating_point<U>::value>::type* = nullptr>
    bool operator()( U lhs , U rhs )
    {
        return lhs == rhs;
    } 
};

Мы используем SFINAE, чтобы отключить/включить различные "специализации" функции в зависимости от параметра шаблона. Поскольку SFINAE может зависеть только от параметров функции, а не от параметров класса, нам нужен необязательный параметр шаблона для функции, которая принимает параметр класса.

Я предпочитаю это решение над наследованием, потому что:

  • Требуется меньше набирать. Меньшая типизация, вероятно, приводит к меньшим ошибкам.
  • Все специализации записываются внутри класса. Таким образом, для написания специализаций сохраняются все специализации внутри исходного класса и делается специализация похожей на функциональные перегрузки, а не на сложный код на основе шаблонов.

Но с компиляторами, которые не реализовали необязательные параметры шаблона функции (например, MSVC в VS2012), это решение не работает, и вы должны использовать решение наследования.

РЕДАКТИРОВАТЬ:. Вы можете проезжать по параметрам-не-реализованным параметрам-функции-шаблона, которые обертывают функцию шаблона другой функцией, которая делегирует работу:

template<typename T>
struct foo
{
private:
    template<typename U>
    void f()
    {
        ...
    }

public:
    void g()
    {
        f<T>();
    }
};

Конечно, компилятор может легко встроить g(), выкидывая вызов обертывания, поэтому на эту альтернативу не влияет производительность.

Ответ 2

Одним из решений было бы перейти от функции, которую вы хотите перегрузить для некоторой реализации, которая зависит от аргументов шаблона классов:

template < typename T >
struct foo {
   void f();
};

template < typename T >
struct f_impl {
    static void impl()
    {
        // default implementation
    }
};

template <>
struct f_impl<int> {
    static void impl()
    {
         // special int implementation
    }
};

template < typename T >
void foo< T >::f()
{
    f_impl< T >::impl();
}

Или просто используйте частные функции, вызывайте их с параметром шаблона и перегружайте их.

template < typename T >
class foo {
public:
    void f()
    {
        impl(T());
    }
private:
    template < typename G >
    void impl( const G& );
    void impl( int );
};

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

Ответ 3

С enable_if:

#include <iostream>
#include <type_traits>

template <typename T>
class A {
    private:
    template <typename U>
    static typename std::enable_if<std::is_same<U, char>::value, char>::type
    g() {
        std::cout << "char\n";
        return char();
    }

    template <typename U>
    static typename std::enable_if<std::is_same<U, int>::value, int>::type
    g() {
        std::cout << "int\n";
        return int();
    }

    public:
    static T f() { return g<T>(); }
};

int main(void)
{
    A<char>::f();
    A<int>::f();
    // error: no matching function for call to ‘A<double>::g()’
    // A<double>::f();
    return 0;
}

Ответ 4

Отправка тегов часто является чистым способом для этого.

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

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

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

Ответ 5

Возможно, я ошибаюсь, но выбран лучший кандидат от Manu343726 имеет ошибку и не будет компилироваться. Обе перегрузки операторов имеют ту же подпись. Рассмотрите лучший вопрос, о котором идет речь std:: enable_if: параметр vs template

P.S. я бы добавил комментарий, но недостаточно репутации, извините