Способ определения правильного предиката для шаблонных типов - PullRequest
1 голос
/ 29 августа 2009

Предположим, у меня есть функция, которая выглядит следующим образом:

template <class In, class In2>
void func(In first, In last, In2 first2);

Я бы хотел, чтобы эта функция вызывала другую функцию, которая принимает предикат. Моим первоначальным инстинктом было сделать что-то вроде этого:

template <class In, class In2>
void func(In first, In last, In2 first2) {
    typedef typename std::iterator_traits<In>::value_type T;
    other_func(first, last, first2, std::less<T>());
}

Но есть проблема, что если In и In2 являются итераторами разных типов? Например, char* против int*. В зависимости от того, какое значение равно In, а какое In2, предикат может усекать значения во время сравнения. Например, если In равно char*, то будет вызываться std::less<char>, даже если In2 является int*.

Когда ::operator< задается два параметра, компилятор может определить правильный тип, и применяются стандартные правила продвижения типов. Тем не менее, при выборе предиката для передачи в функцию, нет возможности сделать это. Есть какой-нибудь умный способ выяснить, какую версию std::less<> я хочу передать, основываясь на In и In2?

EDIT:

Следующий пример иллюстрирует проблему:

unsigned int x = 0x80000000;
unsigned char y = 1;
std::cout << std::less<unsigned char>()(x, y) << std::endl;
std::cout << std::less<unsigned int>()(x, y) << std::endl;

выведет:

1
0

EDIT

Подумав об этом, я действительно хотел бы сделать что-то вроде этого:

typedef typeof(T1() < T2()) T;
other_func(first, last, first2, std::less<T>());

Полагаю, я мог бы использовать расширение __typeof__ в gcc ..., но мне тоже не нравится эта идея. Есть ли способ получить этот чистый эффект стандартным, согласованным способом?

Ответы [ 3 ]

2 голосов
/ 29 августа 2009

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

template <typename T1, typename T2>
struct least_common_promotion;

template <>
struct least_common_promotion<short, int>
{
    typedef int type;
};

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

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

template <class T1, T2>
struct unhomogenous_less : public std::binary_function<T1, T2, bool>
{
   bool operator()(T1 const& l, T2 const& r) const
   { return l < r; }
};

...

typedef typename std::iterator_traits<In>::value_type value_type_1;
typedef typename std::iterator_traits<In2>::value_type value_type_2;
other_func(first, last, first2, unhomogenous_less<value_type_1, value_type_2>());
0 голосов
/ 29 августа 2009

Взяв в качестве примера старую реализацию SGI std::equal, алгоритмы STL справляются с такой ситуацией, имея две версии одного и того же алгоритма: один, использующий встроенный оператор <, который компилятор выводит во время компиляции, и один которая принимает определяемый пользователем двоичный предикат, чтобы пользователь мог использовать любые типы, которые он предпочитает:

template <class _InputIter1, class _InputIter2>
inline bool equal(_InputIter1 __first1, _InputIter1 __last1,
                  _InputIter2 __first2) {
  __STL_REQUIRES(_InputIter1, _InputIterator);
  __STL_REQUIRES(_InputIter2, _InputIterator);
  __STL_REQUIRES(typename iterator_traits<_InputIter1>::value_type,
                 _EqualityComparable);
  __STL_REQUIRES(typename iterator_traits<_InputIter2>::value_type,
                 _EqualityComparable);
  for ( ; __first1 != __last1; ++__first1, ++__first2)
    if (*__first1 != *__first2)
      return false;
  return true;
}

template <class _InputIter1, class _InputIter2, class _BinaryPredicate>
inline bool equal(_InputIter1 __first1, _InputIter1 __last1,
                  _InputIter2 __first2, _BinaryPredicate __binary_pred) {
  __STL_REQUIRES(_InputIter1, _InputIterator);
  __STL_REQUIRES(_InputIter2, _InputIterator);
  for ( ; __first1 != __last1; ++__first1, ++__first2)
    if (!__binary_pred(*__first1, *__first2))
      return false;
  return true;
}

(Примечание: старый код SGI STL взят из здесь .)

0 голосов
/ 29 августа 2009

Если ваши требования к алгоритму таковы, что In value_type не обязательно должен совпадать с типом значения In2, тогда я бы оставил параметры шаблона такими, как у вас; в противном случае они должны быть одинаковыми.

Независимо от того, являются ли они одинаковыми или разными, клиент вашей программы должен выполнить предварительные условия алгоритма, которые вы можете указать. Например, здесь вы можете потребовать, чтобы value_type из In было таким же, как value_type из In2. Если это верно, то функция должна скомпилироваться и быть правильной, как ожидает клиент.

В таком случае вы можете передать std::less<T> экземпляр value_type любого типа шаблона, и все будет в порядке.

Однако, если клиент нарушает это предварительное условие (как в приведенном выше примере, где char не совпадает с int), то клиент, а не вы, исправит компиляцию. ошибка времени.

Убедитесь, что ваш алгоритм хорошо документирован, если не сказать больше:)

...