Скажи, что ты пишешь действительно плохой класс
template <typename T>
class IntFoo
{
T container ;
public:
void add( int val )
{
// made an assumption that
// T will have a method ".push_front".
container.push_front( val ) ;
}
} ;
Игнорировать тот факт, что класс предполагает, что контейнер будет something<int>
Вместо этого обратите внимание на то, что
IntFoo< list<int> > listfoo ;
listfoo.add( 500 ) ; // works
IntFoo< vector<int> > intfoo;
//intfoo.add( 500 ) ; // breaks, _but only if this method is called_..
В общем, это нормально вызывать функцию-член параметризованного типа, как это? Это плохой дизайн? У этого (анти) паттерна есть имя?
Это прекрасно, и называется «утка» во время компиляции и используется во всех местах стандартной библиотеки. А если серьезно, как бы вы сделали что-нибудь полезное с шаблоном, не принимая аргумент шаблона для поддержки определенных функций?
Давайте посмотрим на любой алгоритм в stdlib, например, std::copy
:
template<class InIt, class OutIt>
OutIt copy(InIt first, Init last, OutIt out){
for(; first != last; ++first)
*out++ = *first;
return out;
}
Здесь объект типа InIt
предполагается поддержать operator*()
(для косвенного обращения) и operator++()
для продвижения итератора. Для объекта типа OutIt
предполагается поддержать operator*()
а также и operator++(int)
, Общее предположение также заключается в том, что все, что возвращается из *out++
присваивается (иначе конвертируемый) из любого *first
доходность. Еще одним предположением будет то, что оба InIt
а также OutIt
являются копируемыми.
Другое место, где это используется, находится в любом стандартном контейнере. В C ++ 11, когда вы используете std::vector<T>
, T
должен быть копируемым если и только если Вы используете любую функцию-член, для которой требуется копия.
Все это позволяет определять пользовательские типы так же, как встроенные, то есть они являются гражданами первого класса языка. Давайте снова посмотрим на некоторые алгоритмы, а именно те, которые принимают обратный вызов, который должен применяться к диапазону:
template<class InIt, class UnaryFunction>
InIt for_each(InIt first, InIt last, UnaryFunction f){
for(; first != last; ++first)
f(*first);
return first;
}
InIt
предполагается, чтобы поддерживать те же операции снова, как в copy
пример выше. Тем не менее, теперь у нас также есть UnaryFunction
, Предполагается, что объекты этого типа поддерживают запись вызова функции после исправления, особенно с один аргумент (Унарный). Далее предполагается, что этот параметр этого вызова функции может быть преобразован из любого *first
доходность.
Типичный пример использования этого алгоритма — простая функция:
void print(int i){ std::cout << i << " "; }
int main(){
std::vector<int> v(5); // 5 ints
for(unsigned i=0; i < v.size(); ++i)
v[i] = i;
std::for_each(v.begin(), v.end(), print); // prints '0 1 2 3 4 '
}
Тем не менее, вы также можете использовать функциональные объекты для этого — определенный пользователем тип, который перегружает operator()
:
template<class T>
struct generate_from{
generate_from(T first) : _acc(first) {}
T _acc;
void operator()(T& val){ val = _acc++; }
};
int main(){
std::vector<int> v(5); // 5 ints
// yes, there is std::iota. shush, you.
std::for_each(v.begin(), v.end(), generate_from<int>(0)); // fills 'v' with [0..4]
std::for_each(v.begin(), v.end(), print); // prints '0 1 2 3 4 '
}
Как видите, мой пользовательский тип generate_from
может рассматриваться как функция, она может быть вызвана как будто это была функция. Обратите внимание, что я делаю несколько предположений о T
в generate_from
а именно это должно быть:
operator()
)operator()
)Других решений пока нет …