Как да огранича параметър на шаблон да отговаря на ключ в 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 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, това ще изисква тестване на поведението по време на изпълнение при всички възможни входове.

person Andrew Durward    schedule 18.12.2011
comment
Вярвам, че ключовете също трябва да могат да се конструират или преместват в C++11. - person Omnifarious; 18.12.2011
comment
@Omnifarious това е част от проблема - изискванията не са очевидни без някои предишни познания за магия (магия в смисъл, че не е в кода - поне не е очевидна в кода). Освен това коментарът на Андрю е уместен: изглежда, че все още не можем да знаем дали сме внедрили правилно подреждането на нашия персонализиран клас по време на компилиране: все още трябва да изпълним някакъв тест, за да се уверим, че работи правилно. - 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). Ако изразът в trailing-return-type не е валиден, тогава това конкретно претоварване на 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, но тъй като можете да проверите в документация тази характеристика не винаги работи правилно.

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