boostorg / spirit

Boost.org spirit module
http://boost.org/libs/spirit
394 stars 162 forks source link

`{qi,karma}::symbols` copy constructor does not make an independent copy #760

Open jwwalker opened 1 year ago

jwwalker commented 1 year ago

In Boost 1.82.0 beta 1, the following code

#include <boost/spirit/include/qi.hpp>
#include <iostream>

int main(int argc, const char * argv[])
{
    boost::spirit::qi::symbols<char, int>   one;
    one.at("x") = 1;
    one.at("y") = 2;

    boost::spirit::qi::symbols<char, int>   two( one );
    two.at("x") = 3;

    std::cout << "In one, x = " << one.at("x") <<
        ", y = " << one.at("y") << std::endl;

    return 0;
}

outputs "In one, x = 3, y = 2". I don't think anyone would expect a change to the copy to affect the original. If I instead default-construct two and then do a copy assignment two = one, things behave as expected.

Kojoley commented 1 year ago

It is might be in 'fix and find out' area because proto::deep_copy/BOOST_AUTO/qi::auto will start to do an actual copy then.

A workaround:

    boost::spirit::qi::symbols<char, int>    two;
    two = one;
sehe commented 1 year ago

@Kojoley The copy assignment also doesn't make a deep copy. I answered a similar question Apr 6 2023 on the mailing list:

It's by design, likely to make it efficient and easy (without surprises!) to use symbols in parser expressions. In effect, they get "reference semantics" just like qi::rule<> does when embedded in other rules.

You can easily deep-clone a symbol table if you need:

    template <typename Sym> Sym clone_syms(Sym const& i) {
        Sym o;
        i.for_each(o.add);
        return o;
    }

Here's a live demo driving home the points with more examples: http://coliru.stacked-crooked.com/a/263d22bc47d63375

For completeness here's that demo program with output:

#include <boost/spirit/include/qi.hpp>
#include <iomanip>
using namespace std::placeholders;

template <typename Sym> Sym clone_syms(Sym const& i) {
    Sym o;
    i.for_each(o.add);
    return o;
}

void print(std::string_view s, double v) { std::cout << '[' << quoted(s) << '=' << v << ']'; };

int main() {
    boost::spirit::qi::symbols<char, double> syms1;
    syms1.add("x", 0.0);

    auto syms2 = syms1;
    auto syms3 = clone_syms(syms1);

    syms2.at("x") = 99.5;
    syms2.add("y", 88.5);

    std::cout << "\nsyms1: "; syms1.for_each(print);
    std::cout << "\nsyms2: "; syms2.for_each(print);
    std::cout << "\nsyms3: "; syms3.for_each(print);
}

Printing

syms1: ["x"=99.5]["y"=88.5]
syms2: ["x"=99.5]["y"=88.5]
syms3: ["x"=0]