Предотвратить сравнение равенства родственных структур

У меня есть ряд структур, которые получены из одной и той же базы для повторного использования кода, но я не хочу какой-либо формы полиморфизма.

struct B {
int field;
void doStuff() {}
bool operator==(const B& b) {
return field == b.field;
}
};

struct D1 : public B {
D1(int field) : B{field} {}
};
struct D2 : public B {
D2(int field) : B{field} {}
};

Структуры D1 а также D2 (и более похожие структуры) происходят из B использовать общие поля и методы, чтобы мне не нужно было дублировать эти поля и методы в каждом из производных классов.

Struct B никогда не создается; Я использую только случаи D1 а также D2, Более того, D1 а также D2 не должны взаимодействовать друг с другом вообще. По сути, я не хочу никакого полиморфного поведения: D1 а также D2для всех целей должен действовать как не связанные структуры.

Я хотел бы любой D1 сравнивать с другими D1за равенство, и любой D2 сравнивать с другими D2s для равенства. поскольку D1 а также D2 не содержит полей, было бы целесообразно определить оператор равенства в структуре B,

Однако (как и ожидалось) я получаю следующее взаимодействие между D1 а также D2:

int main() {
D1 d1a(1);
D1 d1b(1);
D2 d2(1);

assert(d1a == d1b); // good

assert(d1a == d2); // oh no, it compiles!
}

Я не хочу иметь возможность сравнивать D1 с D2 объекты, потому что для всех целей они должны действовать так, как будто они не связаны.

Как я могу сделать последнее утверждение ошибкой компиляции без дублирования кода? Определение оператора равенства отдельно для D1 а также D2 (и все другие подобные структуры) будет означать дублирование кода, поэтому я хотел бы избежать этого, если это возможно.

7

Решение

Ты можешь использовать CRTP определить operator == только по ссылке на базовый класс конечного типа:

template<typename T>
struct B {
int field;
void doStuff() {}
bool operator==(const B<T>& b) {
return field == b.field;
}
};

struct D1 : public B<D1> {
D1(int field) : B{field} {}
};
struct D2 : public B<D2> {
D2(int field) : B{field} {}
};

Это вызывает первое assert компилировать и второй отклонить.

4

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

«Структуры D1 и D2 (и более похожие структуры) являются производными от B и используют общие поля и методы»

Тогда сделай B private Базовый класс. Очевидно, что D1 а также D2 не должен разделять свой оператор равенства, так как два оператора принимают разные аргументы. Конечно, вы можете поделиться частью реализации как bool B::equal(B const&) const, поскольку это не будет доступно для внешних пользователей.

7

Вместо определения вашего оператора равенства как части базового класса, вам обычно нужны две функции в производных классах:

struct B {
int field;
void doStuff() {}
};

struct D1 : public B {
D1(int field) : B{field} {}
bool operator==(const D1& d) {
return field == d.field;
}
};
struct D2 : public B {
D2(int field) : B{field} {}
bool operator==(const D2& d) {
return field == d.field;
}
};

Или, как правило, вы можете сделать их свободными функциями:

bool operator==(const D1 &lhs, const D1 &rhs)
{
return lhs.field == rhs.field;
}

bool operator==(const D2 &lhs, const D2 &rhs)
{
return lhs.field == rhs.field;
}

Замечания: Если field не был публичным членом, вам нужно объявить версию бесплатной функции как friend,

Обработка большого количества произвольных типов

Хорошо, так что, возможно, у вас есть D3 через D99Кроме того, некоторые из них являются косвенными потомками B, Вам нужно будет использовать шаблоны:

template <class T>
bool operator==(const T &lhs, const T &rhs)
{
return lhs.field == rhs.field;
}

Большой! Но это хватается все, что плохо для несвязанных типов, которые не должен быть сопоставимым. Поэтому нам нужны ограничения.

Вот тривиальная реализация без дублирования кода (т. Е. Для произвольного числа производных типов):

template <class T, class = std::enable_if<std::is_base_of<B,T>()
&& !std::is_same<B, std::remove_cv_t<std::remove_reference_t<T>>>()>>
bool operator==(const T &lhs, const T &rhs)
{
return lhs.field == rhs.field;
}

enable_if сначала проверяет, что T наследуется от B затем гарантирует, что это не B, Вы заявили в своем вопросе, что B в основном абстрактный тип и никогда не реализуется напрямую, но это тест во время компиляции, так почему бы не быть параноиком?

Как вы позже отметили в комментариях, не все D# получены непосредственно из B, Это все еще будет работать.

Почему у вас возникла эта проблема

Учитывая следующее:

D1 d1(1);
D2 d2(2);
d1 == d2;

Компилятор должен найти оператор сравнения, будь то свободная функция или член D1 (не D2). К счастью, вы определили один в классе B, В третьей строке выше можно эквивалентно указать:

d1.operator==(d2)

operator==Однако является частью Bтак что мы в основном звоним B::operator==(const B &), Почему это работает, когда D2 не является B? Специалист по языку уточнил бы, является ли это технически зависимым от аргументов поиском (ADL) или разрешением перегрузки, но эффект таков, что D2 молча приведен к B как часть вызова функции, что эквивалентно приведенному выше:

d1.operator==(static_cast<B>(d2));

Это происходит потому, что лучшая функция сравнения не может быть найдена. Поскольку альтернативы нет, компилятор выбирает B::operator==(const B &) и делает бросок.

3

Вы можете удалить оператор равенства из исходного определения структуры и заменить его на шаблон функции, принимающий два идентичных типа параметров:

template <class T> bool operator == (const T& lhs, const T& rhs)
{
return lhs.field == rhs.field;
}

Обратите внимание, что эта функция несколько «жадная», лучше всего поместить ее в пространство имен (вместе со структурами, чтобы включить ADL) или дополнительно ограничить такие типы:

#include <type_traits>

template <class T, std::enable_if_t<std::is_base_of_v<B, T>, int> = 0>
bool operator == (const T& lhs, const T& rhs)
{
return lhs.field == rhs.field;
}

(Обратите внимание, что std::is_base_of_v требует C ++ 17, но подробный аналог существует с C ++ 11).

Как последний твик, для предотвращения такой явной реализации:

operator == <B>(d1a,  d2); // ultra-weird usage scenario, but compiles!

или (как указал @Aconcagua в комментариях) вывод типа со ссылками базового класса на производные структуры,

B& b1 = d1a;
B& b2 = d2;

assert(b1 == b2); // Compiles, but see below.

Вы также можете добавить

template <> bool operator == <B>(const B&, const B&) = delete;
2
По вопросам рекламы [email protected]