使用访问者时提升变体转换错误

Posted

技术标签:

【中文标题】使用访问者时提升变体转换错误【英文标题】:Boost Variant conversion error when using visitor 【发布时间】:2020-12-13 22:16:54 【问题描述】:

我并不是要转储代码,但这确实是我可以创建的最小的可重现示例,即使在删除所有逻辑以使其更清晰之后也是如此。

基本上,我正在尝试在 C++ 中实现我自己的一些基本类型版本,通过将类型存储在名为 Values 的 boost::variant 中并使用 boost::static_visitors 在Value 变体。我试图实现的一个操作是not equals 运算符,我创建了一个名为Not_Equal 的访问者来实现这一点。 Not_Equal 运算符使用 SFINAE 和 low_priorityhigh_priority 结构来确定是否允许在操作中使用的两种类型。

Values 变体中的类型为:SpecialIntSpecialBooleanstd::shared_ptr<SeriesInt>std::shared_ptr<SeriesBoolean>SeriesIntSeriesBoolean 是智能指针的原因是因为它们在我的真实版本中存储了大量信息,因此复制它们会很昂贵。

接受的 != 运算符如下:

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 的,因此SpecialIntSpecialBoolean 会导致错误:

错误 C2446 '!=':没有从 'SeriesInt *' 到 'SeriesBoolean *' 的转换

错误 C2446 '!=':没有从 'SeriesBoolean *' 到 'SeriesInt *' 的转换

我不明白它与SeriesBoolean*SeriesInt* 有什么关系,因为它只能接受SpecialIntSpecialBoolean 的类型,但我注意到当我删除带有SpecialIntSpecialBoolean 中的参数表明代码可以正常编译和运行。我需要这些构造函数将值加载到类中(逻辑已删除),所以我的问题是为什么会出现这些错误以及如何解决这个问题?

【问题讨论】:

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 on a!=b:coliru.stacked-crooked.com/a/ed58cdebf04af047 干杯。我在operator&lt;&lt; 实现中有一个小错字。 fixed 和 Sfinae variant 也是 发现了!您提出了很多很好的更改,使其更易于使用,例如对 shared_ptr 的封装。不敢相信我没想到。只是一个查询,您为什么建议对构造函数使用显式? "expensive" 是相对的,但是是的:非trivial 意味着开销可能超出您的预期,尤其是on platforms where the reference count is not lockfree (ad 1)。基本上,如果你不打算分享(并立即取消分享)所有权,要么通过shared_ptr&lt;T&gt; const&amp;,或者更好的是,只是T const&amp; :)

以上是关于使用访问者时提升变体转换错误的主要内容,如果未能解决你的问题,请参考以下文章

无法在 MSVC 19.28 上编译变体访问者访问

为递归变体编写 boost::variant 访问者

无法编译以下代码以访问结构的变体

访问具有单态的变体

将变体数组转换为字符串

Swift 3.1:自定义错误转换为 NSError 以访问其域属性时崩溃