Предотвращение дублирования определений функций в специализациях шаблонов

виджет класс имеет некоторые функции, которые применяются для всех типов параметров (общие функции) и другие функции, которые должны быть специализированы для заданных типов (необычные функции).

g++ настаивает на том, что специализация виджета должна также определять common_fn (), а не только uncommon_fn (), но это в первую очередь противоречит цели использования специализации. Как можно избежать повторения common_fn()?

#include <cassert>

template<typename Type> struct Widget
{
    Widget() {}
    char common_fn() { return 'a'; }
    int uncommon_fn() { return 1; }
};

template<> struct Widget<char>
{
    Widget() {}
    int uncommon_fn() { return 2; }
};

int main()
{
    Widget<char> WidgetChar;
    assert( WidgetChar.common_fn() == 'a' ); // Error
    assert( WidgetChar.uncommon_fn() == 2 );
}

begin-edit

Альф:

Я не могу использовать

template<> int Widget<char>::uncommon_fn() { return 2; }

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

или на самом деле есть способ заставить компилятор распознавать typename Foo::Bar когда пишу

struct Foo { typedef FooBar Bar; };
template<> typename Foo::Bar Widget<Foo>::uncommon_fn() { return ....; }

?

end-edit

begin-edit2

в iammilind:

Это интересно, но я не могу использовать вывод из виджета (или, возможно, более четкое решение рефакторинга общих частей в родительский класс GeneralWidget) по той же причине. Общие части не совсем общие. Их объявления и их определения выглядят одинаково, но поскольку они используют черты, они в конце совсем другое.

end-edit2

4 ответов


#include <assert.h>

template<typename Type> struct Widget
{
    Widget() {}
    char common_fn() { return 'a'; }
    int uncommon_fn() { return 1; }
};

template<>
int Widget<char>::uncommon_fn() { return 2; }

int main()
{
    Widget<char> WidgetChar;
    assert( WidgetChar.common_fn() == 'a' ); // OK
    assert( WidgetChar.uncommon_fn() == 2 );
}

во-первых, это на самом деле не поддерживается в C++. Специализация шаблона требует от вас переопределить все методы, к сожалению.

вы можете поместить свои общие методы в базовый класс, а затем включить их по наследству, хотя мой собственный опыт с этим был смешанным мешком (функция в родительском классе просто не ведет себя так же, как функция в дочернем классе во всех случаях; дружба работает странно, и если базовый класс это также шаблон, вы должны полностью указать его, когда вы хотите использовать любой из его членов [кроме MS compilers]. Операторы тоже никогда не работают так хорошо). Вы можете сделать это, скопировав свои общие методы, чего именно вы пытаетесь избежать. Или, в качестве компромисса при копировании-вставке, вы можете попросить компилятор сделать вашу копию-вставку для вас:

template<> struct Widget<char>
{
    #include "Widget_common.hpart"
    int uncommon_fn() { return 2; }
};

затем вы создаете файл под названием "Widget_common.hpart", который содержит

// This file contains common functions for the Widget<> template, and will be #included
// directly into that template; it should not be included as a standalone header.
char common_fn() { return 'a'; }

вы также можете просто использовать норматив.расширение h, я полагаю. Это определенно злоупотребление препроцессором, но он делает то, что вы просите, избегает головных болей наследования шаблонов (и они действительно довольно болезненны) и позволяет вам хранить только одну копию вашего общего кода. И если кто-то хочет критиковать, какой ужасный kludge это, сделайте это с улучшенным решением ;).


в идеале, в случае class специализация, мы должны специализировать все необходимые методы. Если вы этого не хотите, то работает следующий трюк (с определенными ограничениями):

template<>
struct Widget<char> : Widget<int>
{
  //...
};

Я <int> для простоты, которая, как предполагается, не является специализированной (вы можете поставить что-то вроде Widget<void**> чтобы быть на безопасной стороне).

assert( WidgetChar.common_fn() == 'a' ); // ok

Я посмотрел на ваш другой пост относительно черт, но увидел, что вы, похоже, решили свою проблему, решив не использовать специализацию. Но для тех, у кого была такая же проблема, у меня есть другое решение, которое люди могут захотеть рассмотреть.

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

#include <cassert>

template<typename Type> struct WidgetCommon
{
    char common_fn() { return 'a'; } // 1,000,000 line function here :D
};

template<typename Type> struct Widget
{
    Widget() {}
    char common_fn() { return common.common_fn(); }
    int uncommon_fn() { return 1; }

private:
    WidgetCommon<Type> common;
};

template<> struct Widget<char>
{
    Widget() {}
    char common_fn() { return common.common_fn(); }
    int uncommon_fn() { return 2; }

private:
    WidgetCommon<char> common;
};

int main()
{
    Widget<char> WidgetChar;
    assert( WidgetChar.common_fn() == 'a' );
    assert( WidgetChar.uncommon_fn() == 2 );

    Widget<int> WidgetInt;
    assert( WidgetInt.common_fn() == 'a' );
    assert( WidgetInt.uncommon_fn() == 1 );
}

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