Как мне ограничить параметр шаблона, чтобы он соответствовал ключу в std :: map?

У меня есть шаблон класса, который намеревается использовать свой параметр K в качестве ключа к карте.

Есть ли способ ограничить параметр шаблона типом, который соответствует ключу в std :: map?

Я понимаю, что даже без такого ограничения компилятор выплюнул бы кучу шаблонных ошибок, таких как K, не имеющий operator < (), но было бы неплохо, если бы я мог сделать свой код более очевидным при указании требований.

Решения C ++ 11 приветствуются.

template< typename K >
class Foo
{
  // lots of other code here...

  private:
    std::map< K, size_t > m_map;
};

person kfmfe04    schedule 18.12.2011    source источник
comment
Возможный дубликат: stackoverflow.com/questions/148373/c-restrict-template -функция Если это не тот же вопрос, то хотя бы ответ на него.   -  person Joe McGrath    schedule 18.12.2011
comment
Какое поведение ожидается, если будет передан K, что не подходит для std :: map?   -  person iammilind    schedule 18.12.2011
comment
@JoeMcGrath +1 за полезную ссылку - жаль, что в STL нет чего-то вроде этого: libcds.sourceforge.net/doc/cds-api/   -  person kfmfe04    schedule 18.12.2011


Ответы (4)


Это зависит от того, что вы подразумеваете под словом «соответствует». Если вы хотите убедиться, что K имеет оператор <, вы можете попробовать Библиотеку проверки концепции Boost.

#include "boost/concept_check.hpp"

template< typename K >
class Foo
{
  BOOST_CONCEPT_ASSERT((boost::LessThanComparable< K >));

  // lots of other code here...

  private:
    std::map< K, size_t > m_map;
};

Однако, если вы хотите убедиться, что < определяет StrictWeakOrdering на K, это потребует тестирования поведения во время выполнения при всех возможных входных данных.

person Andrew Durward    schedule 18.12.2011
comment
Я считаю, что ключи также должны быть копируемыми или перемещаемыми в C ++ 11. - person Omnifarious; 18.12.2011
comment
@ В общем, это часть проблемы - требования неочевидны без некоторых предыдущих магических знаний (магия в том смысле, что этого нет в коде - по крайней мере, не очевидна в коде). Кроме того, уместен комментарий Эндрю: мы все еще не можем знать, правильно ли мы реализовали порядок наших пользовательских классов во время компиляции: нам все еще нужно запустить некоторый тест, чтобы убедиться, что он работает правильно. - person kfmfe04; 18.12.2011
comment
+1 Эндрю за упоминание Boost :: Concepts - я думаю, это одна из особенностей, которые didn't сделали это в C ++ 11 ... - person kfmfe04; 18.12.2011
comment
Единственная проблема в том, что вы по-прежнему не получите много ошибок operator<, а затем эту блестящую ошибку, в которой упоминаются проблемы с LessThenComparable. - person UncleBens; 18.12.2011
comment
@ kfmfe04 Да, к сожалению, концепции были исключены из нового стандарта. См. ConceptGCC для реализации (но я считаю, что она больше не поддерживается). - person Andrew Durward; 18.12.2011
comment
@AndrewDurward +1 tyvm за подсказку - попробую! - person kfmfe04; 18.12.2011
comment
@ kfmfe04: Boost :: Concepts - это не та функция. Это (по необходимости) бессвязная попытка имитировать часть функции с помощью существующих конструкций компилятора. - person Omnifarious; 19.12.2011

Версия C ++ 11:

#include <type_traits>

template<class T>
struct satisfies_key_req{
  struct nat{};

  template<class K> static auto test(K* k) -> decltype(*k < *k);
  template<class K> static nat  test(...);

  static bool const value = !std::is_same<decltype(test<T>(0)), nat>::value;
};

#include <iostream>

struct foo{};

int main(){
    static bool const b = satisfies_key_req<int>::value;
    std::cout << b << '\n';
    static bool const b2 = satisfies_key_req<foo>::value;
    std::cout << b2 << '\n';
}

Вывод:

1
0

Ключевым моментом, который я здесь использовал, является выражение SFINAE: auto test(K* k) -> decltype(*k < *k). Если выражение в завершающем-возвращаемом-типе недопустимо, то эта конкретная перегрузка test удаляется из набора перегрузки. Другими словами, это SFINAE'd.

§14.8.2 [temp.deduct]

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

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

p8 Если подстановка приводит к недопустимому типу или выражению, определение типа не выполняется. Недопустимый тип или выражение - это тот, который был бы неправильно сформирован, если бы он был записан с использованием подставленных аргументов. [...]


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

// static_assert, arguably the best choice
template< typename K >
class Foo
{
  static_assert<satisfies_key_req<K>::value, "K does not satisfy key requirements");
  // lots of other code here...

  private:
    std::map< K, size_t > m_map;
};

// new-style SFINAE'd, maybe not really clear
template<
  typename K,
  typename = typename std::enable_if<
               satisfies_key_req<K>::value
             >::type
>
class Foo
{
  // lots of other code here...

  private:
    std::map< K, size_t > m_map;
};

// partial specialization, clarity similar to SFINAE approach
template< 
  typename K,
  bool = satisfies_key_req<K>::value
>
class Foo
{
  // lots of other code here...

  private:
    std::map< K, size_t > m_map;
};

template<typename K>
class Foo<K, false>;
person Xeo    schedule 18.12.2011
comment
+1 в.хорошо! Мне нужно время, чтобы подумать о вашем решении, чтобы осмыслить его последствия ... ... может быть, улучшить это с помощью static_assert (), чтобы мы могли выявлять проблемы во время компиляции? - person kfmfe04; 18.12.2011
comment
@ kfmfe04: я предоставил только признак типа, если вы используете его в static_assert или новом стиле SFINAE, или даже в конструкции, как показано в других ответах (частичная специализация) для запуска ошибки компилятора, - это то, что вам решать. :) Все три подойдут, и я думаю, что static_assert самый ясный. Обратите внимание, что в настоящее время только Clang имеет правильный вывод, версии MSVC и GCC, которые я мог протестировать, все еще не запускают SFINAE на -> decltype(k < k), хотя это законный и стандартный C ++. Я скоро отредактирую некоторые пояснения. - person Xeo; 18.12.2011

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

template <class K, bool = std::is_same<K,int>::value>
class Foo { ... };

template <class K>
class Foo<K,false> { Foo(); };

Итак, если K int, ваш класс работает должным образом. В противном случае вы не сможете построить объект типа Foo<K>. Очевидно, вы можете уточнить условие.

Чтобы проверить, есть ли у K operator<, вы можете использовать boost::has_less, но, как вы можете проверить в documentation эта черта не всегда работает должным образом.

person mattia.penati    schedule 18.12.2011
comment
Вы можете использовать BOOST_STATIC_ASSERT или C ++ 11 static_assert вместо этого шаблона. - person bdonlan; 18.12.2011
comment
@bdonlan +1 за упоминание static_assert. +1 к mattia за упоминание boost::has_less и за добавление ссылки для уточнения. - person kfmfe04; 18.12.2011

Судя по вашему вопросу, я думаю, вам нужна одна подходящая ошибка вместо множества ошибок.

Следующий код находит, имеет ли тип допустимый operator < или нет:

namespace OperatorExist
{
  typedef char no[3]; // '3' can be any awkward number
  template<typename T> no& operator < (const T&, const T&);

  template<typename T>
  struct LessThan {
    static const bool value = (sizeof(*(T*)(0) < *(T*)(0)) != sizeof(no));
  };
}

Теперь вы можете специализировать class Foo, используя указанную выше конструкцию:

template<typename K, bool = OperatorExist::LessThan<K>::value>
class Foo 
{
  // lots of other code here...
  private:
    std::map<K, size_t> m_map;
};

template<typename K>
class Foo<K, false>; // unimplemented for types not suitable for 'std::map'

Как только вы воспользуетесь типом A, несовместимым с std::map, компилятор выдаст единственную ошибку:

error: aggregate ‘Foo<A> oa’ has incomplete type and cannot be defined

Демо

person iammilind    schedule 18.12.2011
comment
Вы правы - я ищу более удобный способ предупредить пользователей класса Foo - интересная реализация, с хорошими сообщениями об ошибках! - person kfmfe04; 18.12.2011