Я не имею в виду дамп кода, но это действительно самый маленький воспроизводимый пример, который я мог создать, даже после удаления всей логики, чтобы сделать его более понятным.
По сути, я пытаюсь реализовать свою собственную версию некоторых базовых типов в C++, чтобы имитировать динамическую типизацию, сохраняя типы в boost::variant с именем Values
и используя boost::static_visitor
для выполнения операций над вариантом Value
. Одна операция, которую я пытаюсь реализовать, — это оператор not equals
, и для этого я создал посетителя с именем Not_Equal
. Оператор Not_Equal
использует SFINAE со структурой low_priority
и high_priority
, чтобы определить, разрешены ли два типа, используемые в операции.
Типы в варианте Values
: {SpecialInt
, SpecialBoolean
, std::shared_ptr<SeriesInt>
, std::shared_ptr<SeriesBoolean>
}.
Причина, по которой SeriesInt
и SeriesBoolean
являются умными указателями, заключается в том, что они хранят много информации в моей реальной версии, поэтому их копирование будет дорогим.
Допустимые операторы != следующие:
SpecialInt != SpecialInt
SpecialBoolean != SpecialBoolean
SeriesInt != SpecialInt
SeriesInt != SeriesInt
SeriesBoolean != SeriesBoolean
как представлено перегрузками операторов в каждом классе.
#include <memory>
#include <boost/variant/static_visitor.hpp>
#include <boost/variant.hpp>
class SpecialBoolean {
public:
SpecialBoolean(bool val) {} //removing this line fixes it
SpecialBoolean() {}
SpecialBoolean operator!= (const SpecialBoolean& rhs) const {
return *this;
}
};
class SpecialInt {
public:
SpecialInt(float val) {} //removing this line fixes it
SpecialInt() {}
SpecialBoolean operator!= (const SpecialInt& rhs) const {
return SpecialBoolean();
}
};
class SeriesBoolean {
public:
SeriesBoolean() {}
std::shared_ptr<SeriesBoolean> operator!= (const SpecialBoolean& rhs) const {
return std::make_shared<SeriesBoolean>();
}
std::shared_ptr<SeriesBoolean> operator!= (const SeriesBoolean& rhs) const {
return std::make_shared<SeriesBoolean>();
}
};
class SeriesInt {
public:
SeriesInt() {}
std::shared_ptr<SeriesBoolean> operator!= (const SpecialInt& rhs) const {
return std::make_shared<SeriesBoolean>();
}
std::shared_ptr<SeriesBoolean> operator!= (const SeriesInt& rhs) const {
return std::make_shared<SeriesBoolean>();
}
};
typedef boost::variant <SpecialInt, SpecialBoolean, std::shared_ptr<SeriesInt>, std::shared_ptr<SeriesBoolean> > Values;
struct low_priority {};
struct high_priority : low_priority {};
struct Not_Equal : public boost::static_visitor<Values> {
auto operator() (Values const& a, Values const& b) const {
return boost::apply_visitor(*this, a, b);
}
template <typename T, typename U>
auto operator() (high_priority, T a, U b) const -> decltype(Values(a != b)) {
return a != b; // problem here
}
template <typename T, typename U>
auto operator() (high_priority, std::shared_ptr<T> a, std::shared_ptr<U> b) const -> decltype(Values(*a != *b)) {
return *a != *b;
}
template <typename T, typename U>
auto operator() (high_priority, std::shared_ptr<T> a, U b) const -> decltype(Values(*a != b)) {
return *a != b;
}
template <typename T, typename U>
Values operator() (low_priority, T, U) const {
throw std::runtime_error("Incompatible arguments");
}
template <typename T, typename U>
Values operator() (T a, U b) const {
return (*this)(high_priority{}, a, b);
}
};
Проблема возникает у посетителя в строке return a != b;
, где типы операторов не являются shared_ptr и, следовательно, либо SpecialInt
, либо SpecialBoolean
, что вызывает ошибки:
Ошибка C2446 '!=': нет преобразования из 'SeriesInt *' в 'SeriesBoolean *'
Ошибка C2446 '!=': нет преобразования из 'SeriesBoolean *' в 'SeriesInt *'
Я не понимаю, какое это имеет отношение к SeriesBoolean*
или SeriesInt*
, поскольку он может принимать только типы SpecialInt
и SpecialBoolean
, но я заметил, что когда я удаляю конструкторы, которые принимают аргумент в SpecialInt
и SpecialBoolean
, код компилируется и запускается как обычно. Мне нужны эти конструкторы для загрузки значений в классы (логика удалена), поэтому мой вопрос: почему я получаю эти ошибки и как я могу это исправить?
Конструкторы для ваших типов приводят к неоднозначным инициализаторам вариантов.
Если можете, подумайте о том, чтобы сделать их явными.
Кроме того, типы возврата decltype на самом деле не имеют смысла, поскольку посетитель возвращает Values
по определению.
Эта перегрузка
template <typename T, typename U>
Values operator()(high_priority, T a, U b) const {
return a != b; // problem here
}
соответствует ВСЕМ комбинациям. operator !=
в таких случаях /не определено/. Вы хотели сделать:
template <typename T>
Values operator()(high_priority, T const& a, T const& b) const {
return a != b; // problem here
}
Теперь вам также понадобится следующая перегрузка:
template <typename T>
Values operator()(high_priority, std::shared_ptr<T> const& a, std::shared_ptr<T> const& b) const {
return *a != *b;
}
В противном случае два идентичных типа аргумента shared_pointer будут неоднозначными.
Эта перегрузка кажется неправильной:
template <typename T, typename U>
Values operator()(high_priority, std::shared_ptr<T> const& a, std::shared_ptr<U> const& b) const {
return *a != *b;
}
Это, очевидно, приведет к проблемам, потому что, например. сравните SeriesInt
с SeriesBool
, который не реализован. Поскольку его нет в вашем списке, отбросьте его.
Точно так же, поскольку
template <typename T, typename U>
Values operator()(high_priority, std::shared_ptr<T> const& a, U const& b) const {
return *a != b;
}
также соответствует, например, [ T = SeriesInt, U = SpecialBoolean ], он не будет компилироваться.
Я бы в основном просмотрел список поддерживаемых перегрузок и просто явно реализовал их. Я буду использовать приведенные выше шаблоны только для случаев 1:1.
Обратите внимание, что последовательное (!) использование аргументов с помощью const& делает выполнение намного более эффективным, особенно для общих указателей.
struct Not_Equal : boost::static_visitor<Values> {
Values operator()(Values const& a, Values const& b) const {
return boost::apply_visitor(*this, a, b);
}
// SpecialInt != SpecialInt
// SpecialBoolean != SpecialBoolean
template <typename T>
Values operator()(T const& a, T const& b) const {
return a != b;
}
// SeriesInt != SeriesInt
// SeriesBoolean != SeriesBoolean
template <typename T>
Values operator()(std::shared_ptr<T> const& a, std::shared_ptr<T> const& b) const {
return *a != *b;
}
// SeriesInt != SpecialInt
Values operator()(std::shared_ptr<SeriesInt> const& a, SpecialInt const& b) const {
return *a != b;
}
template <typename... T>
Values operator()(T const&...) const {
throw std::runtime_error("Incompatible arguments");
}
};
#include <boost/variant.hpp>
#include <boost/variant/static_visitor.hpp>
#include <memory>
struct SpecialBoolean {
explicit SpecialBoolean(bool /*val*/ = false) {}
SpecialBoolean operator!=(const SpecialBoolean& /*rhs*/) const { return *this; }
};
struct SpecialInt {
explicit SpecialInt(float /*val*/ = 0) {}
SpecialBoolean operator!=(const SpecialInt& /*rhs*/) const {
return SpecialBoolean();
}
};
struct SeriesBoolean {
SeriesBoolean() {}
std::shared_ptr<SeriesBoolean> operator!=(const SpecialBoolean& /*rhs*/) const {
return std::make_shared<SeriesBoolean>();
}
std::shared_ptr<SeriesBoolean> operator!=(const SeriesBoolean& /*rhs*/) const {
return std::make_shared<SeriesBoolean>();
}
};
struct SeriesInt {
SeriesInt() {}
std::shared_ptr<SeriesBoolean> operator!=(const SpecialInt& /*rhs*/) const {
return std::make_shared<SeriesBoolean>();
}
std::shared_ptr<SeriesBoolean> operator!=(const SeriesInt& /*rhs*/) const {
return std::make_shared<SeriesBoolean>();
}
};
typedef boost::variant<
SpecialInt,
SpecialBoolean,
std::shared_ptr<SeriesInt>,
std::shared_ptr<SeriesBoolean>
>
Values;
struct Not_Equal : boost::static_visitor<Values> {
Values operator()(Values const& a, Values const& b) const {
return boost::apply_visitor(*this, a, b);
}
// SpecialInt != SpecialInt
// SpecialBoolean != SpecialBoolean
template <typename T>
Values operator()(T const& a, T const& b) const {
return a != b;
}
// SeriesInt != SeriesInt
// SeriesBoolean != SeriesBoolean
template <typename T>
Values operator()(std::shared_ptr<T> const& a, std::shared_ptr<T> const& b) const {
return *a != *b;
}
// SeriesInt != SpecialInt
Values operator()(std::shared_ptr<SeriesInt> const& a, SpecialInt const& b) const {
return *a != b;
}
template <typename... T>
Values operator()(T const&...) const {
throw std::runtime_error("Incompatible arguments");
}
};
int main() {
}
Кроме того, я думаю, вам следует инкапсулировать оптимизацию использования shared_ptr<>, исключив все ваши особые случаи.
Это упрощает все вышеперечисленное:
struct Not_Equal : boost::static_visitor<Values> {
Values operator()(Values const& a, Values const& b) const {
return boost::apply_visitor(*this, a, b);
}
template <typename T>
Values operator()(T const& a, T const& b) const { return a != b; }
Values operator()(SeriesInt const& a, SpecialInt const& b) const { return a != b; }
Values operator()(SeriesBoolean const& a, SpecialBoolean const& b) const { return a != b; }
template <typename... T>
Values operator()(T const&...) const {
throw std::runtime_error("Incompatible arguments");
}
};
Вот полная демонстрация с тестовыми примерами для этого Live On Coliru
#include <boost/variant.hpp>
#include <boost/variant/static_visitor.hpp>
#include <memory>
#include <vector>
#include <iostream>
#include <iomanip>
struct SpecialBoolean {
explicit SpecialBoolean(bool val = false) : val(val) {}
SpecialBoolean operator!=(const SpecialBoolean& rhs) const { return SpecialBoolean{val != rhs.val}; }
private:
bool val;
friend std::ostream& operator<<(std::ostream& os, SpecialBoolean const& b) { return os << "SpecialBoolean{" << std::boolalpha << b.val << "}"; }
};
struct SpecialInt {
explicit SpecialInt(float val = false) : val(val) {}
SpecialBoolean operator!=(const SpecialInt& rhs) const { return SpecialBoolean{val != rhs.val}; }
private:
float val;
friend std::ostream& operator<<(std::ostream& os, SpecialInt const& i) { return os << "SpecialInt{" << i.val << "}"; }
};
struct SeriesBoolean {
SeriesBoolean operator!=(const SpecialBoolean& /*rhs*/) const { return {}; } // TODO
SeriesBoolean operator!=(const SeriesBoolean& /*rhs*/) const { return {}; } // TODO
private:
struct VeryLarge {
std::array<SpecialBoolean, 512> _it_is;
};
std::shared_ptr<VeryLarge> _data = std::make_shared<VeryLarge>();
friend std::ostream& operator<<(std::ostream& os, SeriesBoolean const&) { return os << "SeriesBoolean{...}"; }
};
struct SeriesInt {
SeriesBoolean operator!=(const SpecialInt& /*rhs*/) const { return {}; }
SeriesBoolean operator!=(const SeriesInt& /*rhs*/) const { return {}; }
private:
struct VeryLarge {
std::array<SpecialInt, 512> _it_is;
};
std::shared_ptr<VeryLarge> _data = std::make_shared<VeryLarge>();
friend std::ostream& operator<<(std::ostream& os, SeriesInt const&) { return os << "SeriesInt{...}"; }
};
using Values = boost::variant< SpecialInt, SpecialBoolean, SeriesInt, SeriesBoolean >;
struct Not_Equal : boost::static_visitor<Values> {
Values operator()(Values const& a, Values const& b) const {
return boost::apply_visitor(*this, a, b);
}
template <typename T>
Values operator()(T const& a, T const& b) const { return a != b; }
Values operator()(SeriesInt const& a, SpecialInt const& b) const { return a != b; }
Values operator()(SeriesBoolean const& a, SpecialBoolean const& b) const { return a != b; }
template <typename... T>
Values operator()(T const&...) const {
throw std::runtime_error("Incompatible arguments");
}
};
int main() {
Values const vv[] = {
SpecialInt(42),
SpecialInt(-314e-2),
SpecialBoolean(false),
SpecialBoolean(true),
SeriesInt(),
SeriesBoolean()
};
Not_Equal const neq;
auto col = [](auto const& v, bool right = false) -> auto& {
std::ostringstream ss; // just for quick formatting
ss << v;
if (right)
std::cout << std::right;
else
std::cout << std::left;
return std::cout << std::setw(21) << ss.str();
};
for (auto const& a: vv) for (auto const& b: vv) try {
col(a, true) << " != ";
col(b) << " --> ";
col(neq(a, b)) << "\n";
} catch(std::exception const& e) {
col(e.what()) << "\n";
}
}
Печать
SpecialInt{42} != SpecialInt{42} --> SpecialBoolean{false}
SpecialInt{42} != SpecialInt{-3.14} --> SpecialBoolean{true}
SpecialInt{42} != SpecialBoolean{false} --> Incompatible arguments
SpecialInt{42} != SpecialBoolean{true} --> Incompatible arguments
SpecialInt{42} != SeriesInt{...} --> Incompatible arguments
SpecialInt{42} != SeriesBoolean{...} --> Incompatible arguments
SpecialInt{-3.14} != SpecialInt{42} --> SpecialBoolean{true}
SpecialInt{-3.14} != SpecialInt{-3.14} --> SpecialBoolean{false}
SpecialInt{-3.14} != SpecialBoolean{false} --> Incompatible arguments
SpecialInt{-3.14} != SpecialBoolean{true} --> Incompatible arguments
SpecialInt{-3.14} != SeriesInt{...} --> Incompatible arguments
SpecialInt{-3.14} != SeriesBoolean{...} --> Incompatible arguments
SpecialBoolean{false} != SpecialInt{42} --> Incompatible arguments
SpecialBoolean{false} != SpecialInt{-3.14} --> Incompatible arguments
SpecialBoolean{false} != SpecialBoolean{false} --> SpecialBoolean{false}
SpecialBoolean{false} != SpecialBoolean{true} --> SpecialBoolean{true}
SpecialBoolean{false} != SeriesInt{...} --> Incompatible arguments
SpecialBoolean{false} != SeriesBoolean{...} --> Incompatible arguments
SpecialBoolean{true} != SpecialInt{42} --> Incompatible arguments
SpecialBoolean{true} != SpecialInt{-3.14} --> Incompatible arguments
SpecialBoolean{true} != SpecialBoolean{false} --> SpecialBoolean{true}
SpecialBoolean{true} != SpecialBoolean{true} --> SpecialBoolean{false}
SpecialBoolean{true} != SeriesInt{...} --> Incompatible arguments
SpecialBoolean{true} != SeriesBoolean{...} --> Incompatible arguments
SeriesInt{...} != SpecialInt{42} --> SeriesBoolean{...}
SeriesInt{...} != SpecialInt{-3.14} --> SeriesBoolean{...}
SeriesInt{...} != SpecialBoolean{false} --> Incompatible arguments
SeriesInt{...} != SpecialBoolean{true} --> Incompatible arguments
SeriesInt{...} != SeriesInt{...} --> SeriesBoolean{...}
SeriesInt{...} != SeriesBoolean{...} --> Incompatible arguments
SeriesBoolean{...} != SpecialInt{42} --> Incompatible arguments
SeriesBoolean{...} != SpecialInt{-3.14} --> Incompatible arguments
SeriesBoolean{...} != SpecialBoolean{false} --> SeriesBoolean{...}
SeriesBoolean{...} != SpecialBoolean{true} --> SeriesBoolean{...}
SeriesBoolean{...} != SeriesInt{...} --> Incompatible arguments
SeriesBoolean{...} != SeriesBoolean{...} --> SeriesBoolean{...}
Кроме того, я думаю, вам следует инкапсулировать оптимизацию использования shared_ptr<>, исключив все ваши особые случаи, в комплекте с 36 тестовыми примерами
Как ни странно, с упрощениями можно вдруг просто SFINAE на a!=b
: coliru.stacked-crooked.com/a/ed58cdebf04af047
Ваше здоровье. У меня была небольшая опечатка в реализации operator<<
. исправлено и вариант Sfinae тоже
Замечено! Вы предложили много хороших изменений, которые значительно облегчают работу, например, инкапсуляцию файла shared_ptr. Не могу поверить, что я не подумал об этом. Просто вопрос, почему вы рекомендуете использовать явное для конструктора?
Это позволяет избежать неожиданных неявных преобразований. Вы не столкнетесь с этим легко после упрощений, но посетитель склонен к ним (потому что первая перегрузка требует (Values const&, Values const&)
приглашения неожиданных конверсий. Примечание: вы можете избежать этой путаницы, отделив отправку вариантов от ваших перегрузок посетителя. Я столкнулся с что недавно здесь, где я также объясняю, как я обычно исправляю/избегаю этого.
Спасибо. Также вы упомянули, что лучше всего для производительности передавать std::shared_ptr как const&. Является ли это специфичным для shared_ptr (и других интеллектуальных указателей), потому что это не модуль и, следовательно, имеет дорогостоящие операции копирования?
«дорого» относительно, но да: не-тривиально означает, что накладные расходы могут быть больше, чем вам нравится, особенно на платформах, где счетчик ссылок не является безблокировочным (объявление 1). По сути, если вы не собираетесь делиться (и сразу отменять) право собственности, либо передайте shared_ptr<T> const&
, либо, еще лучше, просто T const&
:)
благослови вас за всю помощь, которую вы оказали, я очень ценю это!
Трюк
high_priority
не SFINAE (а больше похож на отправку тегов). Кроме того, вам это не нужно здесь, см. мой пример.