Я подозреваю, что ограничения должны быть приняты как относящиеся к поведению отношения к значениям, фактически используемым в качестве ключей, не обязательно ко всем значениям типа. В настоящее время у меня нет времени, чтобы пройти стандартный поиск языка «курительного пистолета», который относится к фактическим элементам контейнера, а не ко всем значениям типа.
Аналогичный случай: что если компаратор (для контейнера указателей или интеллектуальных указателей) вызывает виртуальную функцию, и кто-то связывает производный класс сравниваемого типа, который переопределяет виртуальную функцию таким образом, что компаратор не строгий слабый порядок? Не становится ли программа неопределенной, даже если никто не использует этот производный класс?
Если сомневаетесь, вы можете поддержать NaN с помощью компаратора, который является строгим слабым порядком:
bool operator()(double a, double b) {
if ((a == a) && (b == b)) {
return a < b;
}
if ((a != a) && (b != b)) return false;
// We have one NaN and one non-NaN.
// Let's say NaN is less than everything
return (a != a)
}
Последние две строки "оптимизируются" до return (b == b);
, хотя я не уверен, что комментарий оптимизирует с ним.
Я думаю, что Томалак убедил меня, что язык говорит, что весь тип должен быть заказан.
Это не имеет особого смысла, так как карта не вызывает значений из ниоткуда, она использует только те значения, которые ей даны (и копии их), но вопрос касается правил, и их правила, насколько я знать. C ++ 0x - то же самое. Интересно, есть ли отчет о дефекте или какой-либо пункт, отправляющий его.
Это также раздражает в тех (очень редких) системах, где std::less
медленно для указателей, вы не можете использовать <
в качестве компаратора на карте указателей, даже если вы знаете что все указатели на элементы одного и того же массива. Позор.
Другой вариант - использовать следующий класс в качестве типа ключа, поэтому ключи проверяются на наличие NaN только при входе в карту, а не при каждом сравнении, как указано выше.
struct SaneDouble {
double value;
SaneDouble (double d) : value(d) {
if (d != d) throw std::logic_error();
}
static friend bool operator<(SaneDouble lhs, SaneDouble rhs) {
return lhs.value < rhs.value;
}
// possibly a conversion to double
};
Это поднимает другой вопрос - очевидно, что кто-то может создать SaneDouble
, а затем установить для value
значение NaN (при условии, что реализация позволяет получить его откуда-то без сбоев). Так "элементы SaneDouble" строго-слабо упорядочены или нет? Неужели моя нерешительная попытка создать инвариант класса в конструкторе делает мою программу неопределенной, даже если никто не нарушает инвариант просто потому, что они могут и, следовательно, результатом этого являются "элементы SaneDouble"? Действительно ли цель стандарта состоит в том, чтобы поведение программы определялось тогда и только тогда, когда value
помечено private
? Действительно ли стандарт определяет где-нибудь, что такое "элементы" типа?
Интересно, следует ли нам интерпретировать «элементы ключа», чтобы обозначить, что компаратор налагает строгий слабый порядок на некоторые элементы ключа. Предположительно, включая те, которые действительно используются. «У меня есть пончики» не означает, что у меня есть каждый пончик. Хотя это натянуто.