使用访问者时提升变体转换错误
Posted
技术标签:
【中文标题】使用访问者时提升变体转换错误【英文标题】:Boost Variant conversion error when using visitor 【发布时间】:2020-12-13 22:16:54 【问题描述】:我并不是要转储代码,但这确实是我可以创建的最小的可重现示例,即使在删除所有逻辑以使其更清晰之后也是如此。
基本上,我正在尝试在 C++ 中实现我自己的一些基本类型版本,通过将类型存储在名为 Values
的 boost::variant 中并使用 boost::static_visitor
s 在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
中的参数表明代码可以正常编译和运行。我需要这些构造函数将值加载到类中(逻辑已删除),所以我的问题是为什么会出现这些错误以及如何解决这个问题?
【问题讨论】:
high_priority
技巧不是 SFINAE(但更像是标签调度)。另外,这里不需要它,请参阅我的示例。
【参考方案1】:
您的类型的构造器会导致模棱两可的变体初始化器。
如果可以,请考虑将它们明确化。
此外,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& 获取 args 会使执行效率更高,尤其是对于共享指针。
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");
;
现场演示
Live On Coliru
#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 SpecialBooleanval != 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 SpecialBooleanval != 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";
打印
SpecialInt42 != SpecialInt42 --> SpecialBooleanfalse
SpecialInt42 != SpecialInt-3.14 --> SpecialBooleantrue
SpecialInt42 != SpecialBooleanfalse --> Incompatible arguments
SpecialInt42 != SpecialBooleantrue --> Incompatible arguments
SpecialInt42 != SeriesInt... --> Incompatible arguments
SpecialInt42 != SeriesBoolean... --> Incompatible arguments
SpecialInt-3.14 != SpecialInt42 --> SpecialBooleantrue
SpecialInt-3.14 != SpecialInt-3.14 --> SpecialBooleanfalse
SpecialInt-3.14 != SpecialBooleanfalse --> Incompatible arguments
SpecialInt-3.14 != SpecialBooleantrue --> Incompatible arguments
SpecialInt-3.14 != SeriesInt... --> Incompatible arguments
SpecialInt-3.14 != SeriesBoolean... --> Incompatible arguments
SpecialBooleanfalse != SpecialInt42 --> Incompatible arguments
SpecialBooleanfalse != SpecialInt-3.14 --> Incompatible arguments
SpecialBooleanfalse != SpecialBooleanfalse --> SpecialBooleanfalse
SpecialBooleanfalse != SpecialBooleantrue --> SpecialBooleantrue
SpecialBooleanfalse != SeriesInt... --> Incompatible arguments
SpecialBooleanfalse != SeriesBoolean... --> Incompatible arguments
SpecialBooleantrue != SpecialInt42 --> Incompatible arguments
SpecialBooleantrue != SpecialInt-3.14 --> Incompatible arguments
SpecialBooleantrue != SpecialBooleanfalse --> SpecialBooleantrue
SpecialBooleantrue != SpecialBooleantrue --> SpecialBooleanfalse
SpecialBooleantrue != SeriesInt... --> Incompatible arguments
SpecialBooleantrue != SeriesBoolean... --> Incompatible arguments
SeriesInt... != SpecialInt42 --> SeriesBoolean...
SeriesInt... != SpecialInt-3.14 --> SeriesBoolean...
SeriesInt... != SpecialBooleanfalse --> Incompatible arguments
SeriesInt... != SpecialBooleantrue --> Incompatible arguments
SeriesInt... != SeriesInt... --> SeriesBoolean...
SeriesInt... != SeriesBoolean... --> Incompatible arguments
SeriesBoolean... != SpecialInt42 --> Incompatible arguments
SeriesBoolean... != SpecialInt-3.14 --> Incompatible arguments
SeriesBoolean... != SpecialBooleanfalse --> SeriesBoolean...
SeriesBoolean... != SpecialBooleantrue --> SeriesBoolean...
SeriesBoolean... != SeriesInt... --> Incompatible arguments
SeriesBoolean... != SeriesBoolean... --> SeriesBoolean...
【讨论】:
另外,我认为你应该封装使用shared_ptr的优化,消除你所有的特殊情况,complete with 36 testcases 具有讽刺意味的是,您可以突然只需 SFINAE ona!=b
:coliru.stacked-crooked.com/a/ed58cdebf04af047
干杯。我在operator<<
实现中有一个小错字。 fixed 和 Sfinae variant 也是
发现了!您提出了很多很好的更改,使其更易于使用,例如对 shared_ptr 的封装。不敢相信我没想到。只是一个查询,您为什么建议对构造函数使用显式?
"expensive" 是相对的,但是是的:非trivial 意味着开销可能超出您的预期,尤其是on platforms where the reference count is not lockfree (ad 1)。基本上,如果你不打算分享(并立即取消分享)所有权,要么通过shared_ptr<T> const&
,或者更好的是,只是T const&
:)以上是关于使用访问者时提升变体转换错误的主要内容,如果未能解决你的问题,请参考以下文章