Избегание дублирования определений функций в специализированных шаблонах

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

g++ настаивает на том, что специализация для Widget также должна определять 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 );
}

начать-редактирования

в Alf:

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

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 ....; }

?

конец редактировать

начать-edit2

to iammilind:

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

конец edit2

Ответ 1

#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 );
}

Ответ 2

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

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

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

Ответ 3

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

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

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

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

Ответ 4

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

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

#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 );
}

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