У меня есть набор типов, который выглядит так:
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 будет сравниваться с другими, использующими их идентификаторы, но не с остальными данными, которые они содержат.
Я думаю, что это соответствует определению, которое Шон Родитель дал «типу значения», но я также думаю, что это странная или незнакомая (но полезная в моем случае) модель.
Итак, мой вопрос: есть ли название концепции для этой … концепции?
Чем полезен такой тип? Я использую этот тип типа в системе событий «черная доска», которая в основном является набором любого значения, имеющего тип, который является по меньшей мере регулярным.
Однако эта черная доска систематически перезаписывает значение, вставленное (вставленное) в нее, даже если оно уже найдено (посредством сравнения). Таким образом, я перезаписываю полное состояние значения на черной доске, используя операторы сравнения в качестве идентификаторов.
Я понятия не имею, является ли это хорошо известным паттерном или идеей или проблематичным в долгосрочной перспективе. Пока это было очень полезно. Это также похоже на то, что может быть «слишком умным», но мне не хватает опыта с этим шаблоном, чтобы это подтвердить. Возможно, я злоупотребляю использованием операторов сравнения, но мне кажется, что семантика этих типов правильна в моем использовании.
Я могу предоставить подробный пример моего использования в случае необходимости.
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
,
Я думаю, что вы можете найти ответ в Эта бумага от Джона Лакоса, конкретно в разделе фона. Короче Лакос отличает характерные признаки которые составляют стоимость объекта по сравнению с не существенные признаки (Я помню, как их называли случайные признаки, тоже, но может быть не прав в этом), что нет (например, емкость вектора).
Тип имеет правильные операторы сравнения, определяющие общий порядок, и поэтому TotallyOrdered
(с использованием N3351 определение).
Это не различает, сравнивает ли полный порядок все состояние объекта или нет, но, похоже, не существует какой-либо концепции для дифференциации этого. Поскольку было бы невозможно определить ( ==
говорит, что объекты равны в зависимости от сравниваемой части состояния, как вы можете определить, есть ли какая-либо несопоставленная часть?
То, что вы, похоже, описываете, является несущественной частью. Это очень похоже на Capacity () на std :: vector. Понятие Regular определяется в терминах семантики копирования, присваивания и равенства. Пока эта семантика соблюдается, ваш тип Regular. Вы должны решить, что является основной частью вашего типа, решая, что представляет собой этот тип. Основные части, которые вносят вклад в то, что представляет объект, должны быть копиями и включены в сравнение на равенство.
Я думаю, что вы должны различать уровень, на котором вы применяете свои реляционные операторы, и их семантику. Кажется, что ваши операторы имеют правильную семантику, но применяются на запутанном уровне (элемент 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 );
}