简体   繁体   中英

C++17 using std::variant to represent DOM

I'm trying to learn how to use std::variant from C++17. As an example started using it, to represent the DOM. Though this is still work in progress:

  1. I wanted to get expert opinion on the usage of std::variant
  2. also some inputs on the compiler support.

I've the DOM representation code ( Godbolt's Compiler Explorer link ) and it seems to compile fine with Clang trunk . However, I see that the same code does not compile with GCC 8.0.0 20171009 .

I would like to understand what is the reason for this incompatibility. And also if anyone can comment on the "style" and if this is an appropriate usage of 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;
}

Snippet of errors Produced by 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

Element's constructor depends on the type Node, which depends on the other node types.

At the point of the definition of the constructor, these other node types are not complete types (they are defined below).

Solution:

move the body of the constructor below the node type definitions:

#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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM