Существует ли концептуальное имя для обычного типа, для которого сравнения не сравнивают полное состояние объекта?

У меня есть набор типов, который выглядит так:

struct MyFlag
{
SomeId source_id; // INVALID_ID by default
SomeData data; // regular type

friend bool operator==( const MyFlag& a, const MyFlag& b ) { return a.source_id == b.source_id; }
friend bool operator<( const MyFlag& a, const MyFlag& b ) { return a.source_id < b.source_id; }
friend bool operator!=( const MyFlag& a, const MyFlag& b ) { return !(a == b); }

friend bool operator==( const SomeId& a, const MyFlag& b ) { return a == b.source_id; }
friend bool operator<( const SomeId& a, const MyFlag& b ) { return a < b.source_id; }
};MyFlag flag_a { id, data_A };
MyFlag flag_b { id, data_B };

assert( flag_a == flag_b );
assert( flag_a.data != flag_b.data );
assert( flag_a == id );
assert( flag_b == id );

MyFlag flag = flag_b;
assert( flag == flag_a );
assert( flag == id );
assert( flag.data != flag_a.data );

const MyFlag flag_x ={ id_x, data_A };
flag = flag_X;
assert( flag != flag_a );
assert( flag.data == flag_a.data );

То есть, для сравнения рассматривается только определенная часть состояния объекта: в этом примере любой объект MyFlag будет сравниваться с другими, использующими их идентификаторы, но не с остальными данными, которые они содержат.

Я думаю, что это соответствует определению, которое Шон Родитель дал «типу значения», но я также думаю, что это странная или незнакомая (но полезная в моем случае) модель.

Итак, мой вопрос: есть ли название концепции для этой … концепции?


Чем полезен такой тип? Я использую этот тип типа в системе событий «черная доска», которая в основном является набором любого значения, имеющего тип, который является по меньшей мере регулярным.
Однако эта черная доска систематически перезаписывает значение, вставленное (вставленное) в нее, даже если оно уже найдено (посредством сравнения). Таким образом, я перезаписываю полное состояние значения на черной доске, используя операторы сравнения в качестве идентификаторов.

Я понятия не имею, является ли это хорошо известным паттерном или идеей или проблематичным в долгосрочной перспективе. Пока это было очень полезно. Это также похоже на то, что может быть «слишком умным», но мне не хватает опыта с этим шаблоном, чтобы это подтвердить. Возможно, я злоупотребляю использованием операторов сравнения, но мне кажется, что семантика этих типов правильна в моем использовании.

Я могу предоставить подробный пример моего использования в случае необходимости.

4

Решение

MyFlag не является EqualityComparable, поскольку == возвращает true для объектов с разными значениями. Определение EqualityComparable в §3.3 входит axiom { a == b <=> eq(a, b); },

Неформально, eq предназначен для представления равенства того, что мы считаем значение объекта независимо от наличия == для типа этого объекта. Это не совсем то же самое, что репрезентативное равенство, так как (а) разные представления могут считаться равными (например, -0.0 == 0.0), и (b) в представлениях может быть незначительное состояние (разговорно «отступ»).

В случае MyFlagЯ почти уверен, что data будет считаться значимым в значении MyFlag в некотором контексте (несколько экземпляров появляются в самом OP). Формально я мог бы определить оператор cmp над MyFlag как:

bool cmp(const MyFlag& a, const MyFlag& b) {
return a == b && a.data == b.data;
}

который явно обеспечивает более сильную интерпретацию равенства, чем соответствующий operator ==,

Рассмотрим реализацию std::copy:

template <typename In, typename Out>
Out copy_(In first, In last, Out out, std::false_type) {
while(first != last) {
*out++ = *first++;
}
}

template <typename In, typename Out>
Out copy_(In first, In last, Out out, std::true_type) {
while(first != last) {
*out = *first;
*out.data = SomeData();
++first;
++out;
}
}

template <typename In, typename Out>
Out copy(In first, In last, Out out) {
copy_(first, last, out, std::is_same<
Myflag,
typename std::iterator_traits<In>::value_type>());
}

Считаете ли вы это действительной реализацией copy, или вы бы сказали, что это портит данные? Это сохранение равенства в соответствии с Myflag«s operator ==,

В отличие от Myflag был определен как:

class MyFlag
{
SomeData trash_bits;
public:
SomeId source_id; // INVALID_ID by default

friend bool operator==( const MyFlag& a, const MyFlag& b ) { return a.source_id == b.source_id; }
friend bool operator<( const MyFlag& a, const MyFlag& b ) { return a.source_id < b.source_id; }
friend bool operator!=( const MyFlag& a, const MyFlag& b ) { return !(a == b); }

friend bool operator==( const SomeId& a, const MyFlag& b ) { return a == b.source_id; }
friend bool operator<( const SomeId& a, const MyFlag& b ) { return a < b.source_id; }
};

Вы могли бы сделать убедительный аргумент, что trash_bits не являются частью стоимости MyFlag так как они никогда не соблюдаются. затем Я бы согласился что MyFlag является Regular,

2

Другие решения

Я думаю, что вы можете найти ответ в Эта бумага от Джона Лакоса, конкретно в разделе фона. Короче Лакос отличает характерные признаки которые составляют стоимость объекта по сравнению с не существенные признаки (Я помню, как их называли случайные признаки, тоже, но может быть не прав в этом), что нет (например, емкость вектора).

2

Тип имеет правильные операторы сравнения, определяющие общий порядок, и поэтому TotallyOrdered (с использованием N3351 определение).

Это не различает, сравнивает ли полный порядок все состояние объекта или нет, но, похоже, не существует какой-либо концепции для дифференциации этого. Поскольку было бы невозможно определить ( == говорит, что объекты равны в зависимости от сравниваемой части состояния, как вы можете определить, есть ли какая-либо несопоставленная часть?

1

То, что вы, похоже, описываете, является несущественной частью. Это очень похоже на Capacity () на std :: vector. Понятие Regular определяется в терминах семантики копирования, присваивания и равенства. Пока эта семантика соблюдается, ваш тип Regular. Вы должны решить, что является основной частью вашего типа, решая, что представляет собой этот тип. Основные части, которые вносят вклад в то, что представляет объект, должны быть копиями и включены в сравнение на равенство.

1

Я думаю, что вы должны различать уровень, на котором вы применяете свои реляционные операторы, и их семантику. Кажется, что ваши операторы имеют правильную семантику, но применяются на запутанном уровне (элемент ID, а не весь объект).

Во-первых, я бы определил operator== а также operator< в сравнить все состояние объекта. Это наименее удивительный и самый идиоматический способ. Чтобы сравнить только идентификаторы, просто сделайте названный оператор id_equal_to это делает проекцию на элемент данных ID. Если вам нравится, вы можете даже определить смешанные версии (принимая один MyFlag и один SomeID параметр), но это обычно необходимо только для того, чтобы избежать накладных расходов на неявные преобразования. Это не кажется обязательным в этом случае.

Во-вторых, чтобы убедиться, что эти операторы имеют правильная семантика (рефлексивный, симметричный и переходный для operator==и нерефлексивный, асимметричный, транзитивный и общий для operator<), просто определите их с точки зрения std::tie и соответствующий оператор для std::tuple, Вы также должны убедиться, что operator== а также operator< на SomeId также имеет правильную семантику. Для встроенных систем это гарантировано, но для пользовательских типов идентификаторов вы можете применять те же std::tie трюк снова.

#include <cassert>
#include <tuple>

enum { invalid = -1 };
using SomeId = int;   // or any regular type with op== and op<
using SomeData = int; // or any regular type with op== and op<

struct MyFlag
{
SomeId source_id; // INVALID_ID by default
SomeData data;    // regular type

friend bool operator==(MyFlag const& a, MyFlag const& b)
{ return std::tie(a.source_id, a.data) == std::tie(b.source_id, b.data); }

friend bool operator!=(MyFlag const& a, MyFlag const& b)
{ return !(a == b); }

friend bool operator<(MyFlag const& a, MyFlag const& b)
{ return std::tie(a.source_id, a.data) < std::tie(b.source_id, b.data); }

// similarly define >=, >, and <= in terms of !(a < b), (b < a) and !(b < a)

friend bool id_equal_to(MyFlag const& a, MyFlag const& b)
{ return a.source_id == b.source_id; }
};

int main()
{
auto const id = 0;
auto const data_A = 1;
auto const data_B = 2;

MyFlag flag_a { id, data_A };
MyFlag flag_b { id, data_B };

assert( flag_a != flag_b );
assert( id_equal_to(flag_a, flag_b) );
assert( flag_a.data != flag_b.data );

MyFlag flag = flag_b;
assert( flag != flag_a );
assert( id_equal_to(flag, flag_a) );
assert( flag.data != flag_a.data );

auto const id_x = invalid;
const MyFlag flag_x = { id_x, data_A };
flag = flag_x;
assert( flag != flag_a );
assert( id_equal_to(flag, flag_x) );
assert( !id_equal_to(flag, flag_a) );
assert( flag.data == flag_a.data );
}

Живой пример.

0