【问题标题】:C++17 using std::variant to represent DOMC++17 使用 std::variant 表示 DOM
【发布时间】:2018-03-25 01:35:48
【问题描述】:

我正在尝试从 C++17 学习如何使用std::variant。作为一个开始使用它的示例,来表示 DOM。虽然这仍在进行中:

  1. 我想就std::variant的用法征求专家意见
  2. 还有一些关于编译器支持的意见。

我有DOM representation code (Godbolt's Compiler Explorer link),它似乎与Clang trunk 一起编译得很好。但是,我看到相同的代码不能用GCC 8.0.0 20171009 编译。

我想了解造成这种不兼容性的原因是什么。还有是否有人可以评论“风格”,如果这是std::variant的适当用法。

#include <vector>
#include <type_traits>
#include <string>
#include <variant>
#include <unordered_map>

// https://dom.spec.whatwg.org/ : reference for the DOM.

namespace dragon {
    namespace dom {
        namespace traits_ns {
            template <bool valid_node_type = false>
            struct valid_node {
                static constexpr bool value = valid_node_type;
            };

            template <typename T>
            struct is_valid_node {
                static constexpr bool value = std::is_base_of<valid_node<true>, T>::value && T::value;
            };

            // TODO: Need to define policy concepts which will check if the children 
            // of each node is of the right, allowed type.
        }

        struct Element;
        struct Text;
        struct Document;
        struct Document_type;
        struct Document_fragment;
        struct Processing_instruction;
        struct Comment;

        using AttrList = std::vector<std::pair<std::string, std::string>>;
        using AttrSet = std::unordered_map<std::string, std::string>;
        using Node = std::variant<Document, Document_type, Document_fragment, Element, Text, Processing_instruction, Comment>;
        using Children = std::vector<Node>;


        struct Element final : traits_ns::valid_node<true> {
            Element(std::string t, const Children& c, const AttrList& a) : tag{ t }, child{ c } {}
            Element() = default;
            ~Element() = default;
            std::string tag;
            Children child;
            AttrSet attr;
        };

        struct Text final : traits_ns::valid_node<true> {
            explicit Text(std::string d) : data{ d } {}
            std::string data;
        };

        struct Document final : traits_ns::valid_node<true> {
            Document() = default;
            ~Document() = default;
        };

        struct Document_type final : traits_ns::valid_node<true> {};
        struct Document_fragment final : traits_ns::valid_node<true> {};
        struct Processing_instruction final : traits_ns::valid_node<true> {};
        struct Comment final : traits_ns::valid_node<true> {};

    } // namespace dom
} // namespace dragon


namespace dom = dragon::dom;
using std::vector;

int main()
{
    dom::Node t{dom::Text{"Hello"}};
    dom::Node e{dom::Element{"element", {}, {{"class", "blah"}}}};
    return 0;
}

GCC 产生的错误片段:

        #1 with x86-64 gcc (trunk)

    In file included from /opt/compiler-explorer/gcc-trunk-20171009/include/c++/8.0.0/bits/move.h:55:0,
                     from /opt/compiler-explorer/gcc-trunk-20171009/include/c++/8.0.0/bits/stl_pair.h:59,
                     from /opt/compiler-explorer/gcc-trunk-20171009/include/c++/8.0.0/bits/stl_algobase.h:64,
                     from /opt/compiler-explorer/gcc-trunk-20171009/include/c++/8.0.0/vector:60,
                     from <source>:1:
    /opt/compiler-explorer/gcc-trunk-20171009/include/c++/8.0.0/type_traits: In instantiation of 'struct std::is_trivially_destructible<dragon::dom::Document>':
    /opt/compiler-explorer/gcc-trunk-20171009/include/c++/8.0.0/type_traits:2859:25:   required from 'constexpr const bool std::is_trivially_destructible_v<dragon::dom::Document>'
    /opt/compiler-explorer/gcc-trunk-20171009/include/c++/8.0.0/variant:309:5:   required from 'constexpr const bool std::__detail::__variant::_Traits<dragon::dom::Document, dragon::dom::Document_type, dragon::dom::Document_fragment, dragon::dom::Element, dragon::dom::Text, dragon::dom::Processing_instruction, dragon::dom::Comment>::_S_trivial_dtor'
    /opt/compiler-explorer/gcc-trunk-20171009/include/c++/8.0.0/variant:317:20:   required from 'constexpr const bool std::__detail::__variant::_Traits<dragon::dom::Document, dragon::dom::Document_type, dragon::dom::Document_fragment, dragon::dom::Element, dragon::dom::Text, dragon::dom::Processing_instruction, dragon::dom::Comment>::_S_trivial_move_assign'
    /opt/compiler-explorer/gcc-trunk-20171009/include/c++/8.0.0/variant:642:16:   required by substitution of 'template<class ... _Types> using _Move_assign_alias = std::__detail::__variant::_Move_assign_base<std::__detail::__variant::_Traits<_Types>::_S_trivial_move_assign, _Types ...> [with _Types = {dragon::dom::Document, dragon::dom::Document_type, dragon::dom::Document_fragment, dragon::dom::Element, dragon::dom::Text, dragon::dom::Processing_instruction, dragon::dom::Comment}]'
    /opt/compiler-explorer/gcc-trunk-20171009/include/c++/8.0.0/variant:645:12:   required from 'struct std::__detail::__variant::_Variant_base<dragon::dom::Document, dragon::dom::Document_type, dragon::dom::Document_fragment, dragon::dom::Element, dragon::dom::Text, dragon::dom::Processing_instruction, dragon::dom::Comment>'
    /opt/compiler-explorer/gcc-trunk-20171009/include/c++/8.0.0/variant:1032:11:   required from 'class std::variant<dragon::dom::Document, dragon::dom::Document_type, dragon::dom::Document_fragment, dragon::dom::Element, dragon::dom::Text, dragon::dom::Processing_instruction, dragon::dom::Comment>'
    41 : <source>:41:86:   required from here
    /opt/compiler-explorer/gcc-trunk-20171009/include/c++/8.0.0/type_traits:1285:12: error: invalid use of incomplete type 'struct dragon::dom::Document'
         struct is_trivially_destructible
                ^~~~~~~~~~~~~~~~~~~~~~~~~
    28 : <source>:28:10: note: forward declaration of 'struct dragon::dom::Document'
       struct Document;
              ^~~~~~~~
    In file included from /opt/compiler-explorer/gcc-trunk-20171009/include/c++/8.0.0/bits/move.h:55:0,
                     from /opt/compiler-explorer/gcc-trunk-20171009/include/c++/8.0.0/bits/stl_pair.h:59,
                     from /opt/compiler-explorer/gcc-trunk-20171009/include/c++/8.0.0/bits/stl_algobase.h:64,
                     from /opt/compiler-explorer/gcc-trunk-20171009/include/c++/8.0.0/vector:60,
                     from <source>:1:
    /opt/compiler-explorer/gcc-trunk-20171009/include/c++/8.0.0/type_traits: In instantiation of 'constexpr const bool std::is_trivially_destructible_v<dragon::dom::Document>':
    /opt/compiler-explorer/gcc-trunk-20171009/include/c++/8.0.0/variant:309:5:   required from 'constexpr const bool std::__detail::__variant::_Traits<dragon::dom::Document, dragon::dom::Document_type, dragon::dom::Document_fragment, dragon::dom::Element, dragon::dom::Text, dragon::dom::Processing_instruction, dragon::dom::Comment>::_S_trivial_dtor'
    /opt/compiler-explorer/gcc-trunk-20171009/include/c++/8.0.0/variant:317:20:   required from 'constexpr const bool std::__detail::__variant::_Traits<dragon::dom::Document, dragon::dom::Document_type, dragon::dom::Document_fragment, dragon::dom::Element, dragon::dom::Text, dragon::dom::Processing_instruction, dragon::dom::Comment>::_S_trivial_move_assign'

<SNIP> 

dragon::dom::Document_fragment, dragon::dom::Element, dragon::dom::Text, dragon::dom::Processing_instruction, dragon::dom::Comment}]' is implicitly deleted because the default definition would be ill-formed:
           variant(const variant& __rhs) = default;
           ^~~~~~~
    /opt/compiler-explorer/gcc-trunk-20171009/include/c++/8.0.0/variant:1087:7: error: use of deleted function 'constexpr std::_Enable_copy_move<false, false, false, false, _Tag>::_Enable_copy_move(const std::_Enable_copy_move<false, false, false, false, _Tag>&) [with _Tag = std::variant<dragon::dom::Document, dragon::dom::Document_type, dragon::dom::Document_fragment, dragon::dom::Element, dragon::dom::Text, dragon::dom::Processing_instruction, dragon::dom::Comment>]'
    In file included from /opt/compiler-explorer/gcc-trunk-20171009/include/c++/8.0.0/variant:38:0,
                     from <source>:4:
    /opt/compiler-explorer/gcc-trunk-20171009/include/c++/8.0.0/bits/enable_special_members.h:301:15: note: declared here
         constexpr _Enable_copy_move(_Enable_copy_move const&) noexcept  = delete;
                   ^~~~~~~~~~~~~~~~~
    Compiler exited with result code 1

        x86-64 gcc (trunk) (Editor #1, Compiler #1)

    g++ (GCC-Explorer-Build) 8.0.0 20171009 (experimental)- cached

【问题讨论】:

  • 您好,您的问题似乎有两个部分:(1)为什么代码无法编译,(2)这是好代码吗?第二部分应发布到 codereview.stackexchange.com。对于第一部分,请提供一个足以重现 GCC 编译错误的 minimal 示例。
  • @Brian 感谢 Brian,将发布到代码审查。对于编译器错误,提供的链接也应该给出错误。如果它没有显示,请告诉我。谢谢。
  • 您应该在此处提供MCVE 的代码版本
  • @Brian 这是我的代码版本。我已经给出了为我的代码生成的链接编译器资源管理器。不够?谢谢。
  • stackoverflow.com 上的所有问题都必须以纯文本形式包含问题本身中的所有相关信息。一个“提供的链接”,将来可能随时停止工作,使问题变得毫无意义,这是不够的。除非您编辑问题并在问题本身中提供minimal reproducible example,否则它将作为题外话关闭。

标签: c++ dom c++17 variant


【解决方案1】:

元素的构造函数依赖于节点类型,节点类型依赖于其他节点类型。

在构造函数的定义点上,这些其他节点类型都不是完整的类型(它们在下面定义)。

解决办法:

将构造函数的主体移到节点类型定义的下方:

#include <vector>
#include <type_traits>
#include <string>
#include <variant>
#include <unordered_map>

// https://dom.spec.whatwg.org/ : reference for the DOM.

namespace dragon {
    namespace dom {
        namespace traits_ns {
            template <bool valid_node_type = false>
            struct valid_node {
                static constexpr bool value = valid_node_type;
            };

            template <typename T>
            struct is_valid_node {
                static constexpr bool value = std::is_base_of<valid_node<true>, T>::value && T::value;
            };

            // TODO: Need to define policy concepts which will check if the children 
            // of each node is of the right, allowed type.
        }

        struct Element;
        struct Text;
        struct Document;
        struct Document_type;
        struct Document_fragment;
        struct Processing_instruction;
        struct Comment;

        using AttrList = std::vector<std::pair<std::string, std::string>>;
        using AttrSet = std::unordered_map<std::string, std::string>;
        using Node = std::variant<Document, Document_type, Document_fragment, Element, Text, Processing_instruction, Comment>;
        using Children = std::vector<Node>;


        struct Element final : traits_ns::valid_node<true> {
            Element(std::string t, const Children& c, const AttrList& a);
            Element() = default;
            ~Element() = default;
            std::string tag;
            Children child;
            AttrSet attr;
        };

        struct Text final : traits_ns::valid_node<true> {
            explicit Text(std::string d) : data{ d } {}
            std::string data;
        };

        struct Document final : traits_ns::valid_node<true> {
            Document() = default;
            ~Document() = default;
        };

        struct Document_type final : traits_ns::valid_node<true> {};
        struct Document_fragment final : traits_ns::valid_node<true> {};
        struct Processing_instruction final : traits_ns::valid_node<true> {};
        struct Comment final : traits_ns::valid_node<true> {};

        Element::Element(std::string t, const Children& c, const AttrList& a) : tag{ t }, child{ c } {}

    } // namespace dom
} // namespace dragon


namespace dom = dragon::dom;
using std::vector;

int main()
{
    dom::Node t{dom::Text{"Hello"}};
    dom::Node e{dom::Element{"element", {}, {{"class", "blah"}}}};
    return 0;
}

【讨论】:

  • 谢谢!只是好奇为什么clang没有抱怨它! GCC 和 Visual Studio 似乎不喜欢它。标准中是否定义了行为?
  • @user3169543 clang 拥有传奇般的宽容度。我在使用 clang、gcc 和 MSVC 的环境中工作。我们总是在 gcc 上编译以发现其他编译器遗漏的细微错误。
  • @user3169543 你会遇到的下一个问题是std::variant 不支持递归(boost 有 recursive_wrapper)。我对树状结构的强烈建议是转储 std::variant 并使用 boost。否则,您最终将编写自己的可克隆包装器类型,这只是重新发明一个非常好的(并且经过测试!)轮子的一个案例。
猜你喜欢
  • 1970-01-01
  • 2017-03-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-11
  • 2023-03-19
  • 2021-10-26
  • 1970-01-01
相关资源
最近更新 更多