Как ограничить параметр шаблона, чтобы он соответствовал ключу в std :: map? - PullRequest
2 голосов
/ 18 декабря 2011

У меня есть шаблон класса, который намеревается использовать свой параметр 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;
};

Ответы [ 4 ]

6 голосов
/ 18 декабря 2011

Это зависит от того, что вы подразумеваете под "соответствует".Если вы хотите проверить, что K имеет оператор <, вы можете попробовать Boost Concept Checking Library .

#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, что потребует тестирования поведения во время выполнения при всех возможных входах.

4 голосов
/ 18 декабря 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 удаляется из набора перегрузок. Другими словами, это СФИНАЕ.

§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>;
2 голосов
/ 18 декабря 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, но, как вы можете проверить в документации , эта черта неработать правильно всегда.

1 голос
/ 18 декабря 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

Демо

...