Idiomatic way to prevent slicing?





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty{ height:90px;width:728px;box-sizing:border-box;
}







9















Sometimes it can be an annoyance that c++ defaults to allow slicing. For example



#include <iostream>
struct foo { int a; };
struct bar : foo { int b; };

int main() {
bar x{1,2};
foo y = x; // <- I dont want this to compile!
}


This compiles and runs as expected! Though, what if I dont want to enable slicing?



What is the idomatic way to write foo such that one cannot slice instances of any derived class?










share|improve this question































    9















    Sometimes it can be an annoyance that c++ defaults to allow slicing. For example



    #include <iostream>
    struct foo { int a; };
    struct bar : foo { int b; };

    int main() {
    bar x{1,2};
    foo y = x; // <- I dont want this to compile!
    }


    This compiles and runs as expected! Though, what if I dont want to enable slicing?



    What is the idomatic way to write foo such that one cannot slice instances of any derived class?










    share|improve this question



























      9












      9








      9


      3






      Sometimes it can be an annoyance that c++ defaults to allow slicing. For example



      #include <iostream>
      struct foo { int a; };
      struct bar : foo { int b; };

      int main() {
      bar x{1,2};
      foo y = x; // <- I dont want this to compile!
      }


      This compiles and runs as expected! Though, what if I dont want to enable slicing?



      What is the idomatic way to write foo such that one cannot slice instances of any derived class?










      share|improve this question
















      Sometimes it can be an annoyance that c++ defaults to allow slicing. For example



      #include <iostream>
      struct foo { int a; };
      struct bar : foo { int b; };

      int main() {
      bar x{1,2};
      foo y = x; // <- I dont want this to compile!
      }


      This compiles and runs as expected! Though, what if I dont want to enable slicing?



      What is the idomatic way to write foo such that one cannot slice instances of any derived class?







      c++ inheritance object-slicing






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited 5 hours ago









      rrauenza

      3,55921835




      3,55921835










      asked 8 hours ago









      user463035818user463035818

      18.8k42970




      18.8k42970
























          3 Answers
          3






          active

          oldest

          votes


















          12














          I'm not sure if there is a named idiom for it but you can add a deleted function to the overload set that is a better match then the base classes slicing operations. If you change foo to



          struct foo 
          {
          int a;
          foo() = default; // you have to add this because of the template constructor

          template<typename T>
          foo(const T&) = delete; // error trying to copy anything but a foo

          template<typename T>
          foo& operator=(const T&) = delete; // error assigning anything else but a foo
          };


          then you can only ever copy construct or copy assign a foo to foo. Any other type will pick the function template and you'll get an error about using a deleted function. This does mean that your class, and the classes that use it can no longer be an aggregate though. Since the members that are added are templates, they are not considered copy constructors or copy assignment operators so you'll get the default copy and move constructors and assignment operators.






          share|improve this answer


























          • Note that this doesn't prevent explicit slicing like this: foo y = static_cast<foo&>(x);. That said, perhaps it's not a problem to OP.

            – eerorika
            8 hours ago











          • if I understand correctly this is a nice way to prevent implicit conversions for function parameters in general

            – user463035818
            8 hours ago






          • 1





            @user463035818 Yep. I've been using it since I've asked that Q.

            – NathanOliver
            8 hours ago






          • 3





            I look at it as reverse SFINAE. You make the overloads you want to compile, and then add a deleted template stopping everything else.

            – NathanOliver
            8 hours ago





















          4














          Since 2011, the idiomatic way has been to use auto:



          #include <iostream>
          struct foo { int a; };
          struct bar : foo { int b; };

          int main() {
          bar x{1,2};
          auto y = x; // <- y is a bar
          }


          If you wish to actively prevent slicing, there are a number of ways:



          Usually the most preferable way, unless you specifically need inheritance (you often don't) is to use encapsulation:



          #include <iostream>

          struct foo { int a; };
          struct bar
          {
          bar(int a, int b)
          : foo_(a)
          , b(b)
          {}

          int b;

          int get_a() const { return foo_.a; }

          private:
          foo foo_;
          };

          int main() {
          bar x{1,2};
          // foo y = x; // <- does not compile

          }


          Another more specialised way might be to alter the permissions around copy operators:



          #include <iostream>

          struct foo {
          int a;
          protected:
          foo(foo const&) = default;
          foo(foo&&) = default;
          foo& operator=(foo const&) = default;
          foo& operator=(foo&&) = default;

          };

          struct bar : foo
          {
          bar(int a, int b)
          : foo{a}, b{b}
          {}

          int b;
          };

          int main() {
          auto x = bar (1,2);
          // foo y = x; // <- does not compile
          }





          share|improve this answer































            3














            You can prevent the base from being copied outside of member functions of derived classes and the base itself by declaring the copy constructor protected:



            struct foo {
            // ...
            protected:
            foo(foo&) = default;
            };





            share|improve this answer



















            • 4





              but then I cannot copy foos anymore :( I'd like to prevent only copying a bar to a foo if possible

              – user463035818
              8 hours ago












            Your Answer






            StackExchange.ifUsing("editor", function () {
            StackExchange.using("externalEditor", function () {
            StackExchange.using("snippets", function () {
            StackExchange.snippets.init();
            });
            });
            }, "code-snippets");

            StackExchange.ready(function() {
            var channelOptions = {
            tags: "".split(" "),
            id: "1"
            };
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function() {
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled) {
            StackExchange.using("snippets", function() {
            createEditor();
            });
            }
            else {
            createEditor();
            }
            });

            function createEditor() {
            StackExchange.prepareEditor({
            heartbeatType: 'answer',
            autoActivateHeartbeat: false,
            convertImagesToLinks: true,
            noModals: true,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: 10,
            bindNavPrevention: true,
            postfix: "",
            imageUploader: {
            brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
            contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
            allowUrls: true
            },
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            });


            }
            });














            draft saved

            draft discarded


















            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55600025%2fidiomatic-way-to-prevent-slicing%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown

























            3 Answers
            3






            active

            oldest

            votes








            3 Answers
            3






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes









            12














            I'm not sure if there is a named idiom for it but you can add a deleted function to the overload set that is a better match then the base classes slicing operations. If you change foo to



            struct foo 
            {
            int a;
            foo() = default; // you have to add this because of the template constructor

            template<typename T>
            foo(const T&) = delete; // error trying to copy anything but a foo

            template<typename T>
            foo& operator=(const T&) = delete; // error assigning anything else but a foo
            };


            then you can only ever copy construct or copy assign a foo to foo. Any other type will pick the function template and you'll get an error about using a deleted function. This does mean that your class, and the classes that use it can no longer be an aggregate though. Since the members that are added are templates, they are not considered copy constructors or copy assignment operators so you'll get the default copy and move constructors and assignment operators.






            share|improve this answer


























            • Note that this doesn't prevent explicit slicing like this: foo y = static_cast<foo&>(x);. That said, perhaps it's not a problem to OP.

              – eerorika
              8 hours ago











            • if I understand correctly this is a nice way to prevent implicit conversions for function parameters in general

              – user463035818
              8 hours ago






            • 1





              @user463035818 Yep. I've been using it since I've asked that Q.

              – NathanOliver
              8 hours ago






            • 3





              I look at it as reverse SFINAE. You make the overloads you want to compile, and then add a deleted template stopping everything else.

              – NathanOliver
              8 hours ago


















            12














            I'm not sure if there is a named idiom for it but you can add a deleted function to the overload set that is a better match then the base classes slicing operations. If you change foo to



            struct foo 
            {
            int a;
            foo() = default; // you have to add this because of the template constructor

            template<typename T>
            foo(const T&) = delete; // error trying to copy anything but a foo

            template<typename T>
            foo& operator=(const T&) = delete; // error assigning anything else but a foo
            };


            then you can only ever copy construct or copy assign a foo to foo. Any other type will pick the function template and you'll get an error about using a deleted function. This does mean that your class, and the classes that use it can no longer be an aggregate though. Since the members that are added are templates, they are not considered copy constructors or copy assignment operators so you'll get the default copy and move constructors and assignment operators.






            share|improve this answer


























            • Note that this doesn't prevent explicit slicing like this: foo y = static_cast<foo&>(x);. That said, perhaps it's not a problem to OP.

              – eerorika
              8 hours ago











            • if I understand correctly this is a nice way to prevent implicit conversions for function parameters in general

              – user463035818
              8 hours ago






            • 1





              @user463035818 Yep. I've been using it since I've asked that Q.

              – NathanOliver
              8 hours ago






            • 3





              I look at it as reverse SFINAE. You make the overloads you want to compile, and then add a deleted template stopping everything else.

              – NathanOliver
              8 hours ago
















            12












            12








            12







            I'm not sure if there is a named idiom for it but you can add a deleted function to the overload set that is a better match then the base classes slicing operations. If you change foo to



            struct foo 
            {
            int a;
            foo() = default; // you have to add this because of the template constructor

            template<typename T>
            foo(const T&) = delete; // error trying to copy anything but a foo

            template<typename T>
            foo& operator=(const T&) = delete; // error assigning anything else but a foo
            };


            then you can only ever copy construct or copy assign a foo to foo. Any other type will pick the function template and you'll get an error about using a deleted function. This does mean that your class, and the classes that use it can no longer be an aggregate though. Since the members that are added are templates, they are not considered copy constructors or copy assignment operators so you'll get the default copy and move constructors and assignment operators.






            share|improve this answer















            I'm not sure if there is a named idiom for it but you can add a deleted function to the overload set that is a better match then the base classes slicing operations. If you change foo to



            struct foo 
            {
            int a;
            foo() = default; // you have to add this because of the template constructor

            template<typename T>
            foo(const T&) = delete; // error trying to copy anything but a foo

            template<typename T>
            foo& operator=(const T&) = delete; // error assigning anything else but a foo
            };


            then you can only ever copy construct or copy assign a foo to foo. Any other type will pick the function template and you'll get an error about using a deleted function. This does mean that your class, and the classes that use it can no longer be an aggregate though. Since the members that are added are templates, they are not considered copy constructors or copy assignment operators so you'll get the default copy and move constructors and assignment operators.







            share|improve this answer














            share|improve this answer



            share|improve this answer








            edited 8 hours ago

























            answered 8 hours ago









            NathanOliverNathanOliver

            98.5k16138218




            98.5k16138218













            • Note that this doesn't prevent explicit slicing like this: foo y = static_cast<foo&>(x);. That said, perhaps it's not a problem to OP.

              – eerorika
              8 hours ago











            • if I understand correctly this is a nice way to prevent implicit conversions for function parameters in general

              – user463035818
              8 hours ago






            • 1





              @user463035818 Yep. I've been using it since I've asked that Q.

              – NathanOliver
              8 hours ago






            • 3





              I look at it as reverse SFINAE. You make the overloads you want to compile, and then add a deleted template stopping everything else.

              – NathanOliver
              8 hours ago





















            • Note that this doesn't prevent explicit slicing like this: foo y = static_cast<foo&>(x);. That said, perhaps it's not a problem to OP.

              – eerorika
              8 hours ago











            • if I understand correctly this is a nice way to prevent implicit conversions for function parameters in general

              – user463035818
              8 hours ago






            • 1





              @user463035818 Yep. I've been using it since I've asked that Q.

              – NathanOliver
              8 hours ago






            • 3





              I look at it as reverse SFINAE. You make the overloads you want to compile, and then add a deleted template stopping everything else.

              – NathanOliver
              8 hours ago



















            Note that this doesn't prevent explicit slicing like this: foo y = static_cast<foo&>(x);. That said, perhaps it's not a problem to OP.

            – eerorika
            8 hours ago





            Note that this doesn't prevent explicit slicing like this: foo y = static_cast<foo&>(x);. That said, perhaps it's not a problem to OP.

            – eerorika
            8 hours ago













            if I understand correctly this is a nice way to prevent implicit conversions for function parameters in general

            – user463035818
            8 hours ago





            if I understand correctly this is a nice way to prevent implicit conversions for function parameters in general

            – user463035818
            8 hours ago




            1




            1





            @user463035818 Yep. I've been using it since I've asked that Q.

            – NathanOliver
            8 hours ago





            @user463035818 Yep. I've been using it since I've asked that Q.

            – NathanOliver
            8 hours ago




            3




            3





            I look at it as reverse SFINAE. You make the overloads you want to compile, and then add a deleted template stopping everything else.

            – NathanOliver
            8 hours ago







            I look at it as reverse SFINAE. You make the overloads you want to compile, and then add a deleted template stopping everything else.

            – NathanOliver
            8 hours ago















            4














            Since 2011, the idiomatic way has been to use auto:



            #include <iostream>
            struct foo { int a; };
            struct bar : foo { int b; };

            int main() {
            bar x{1,2};
            auto y = x; // <- y is a bar
            }


            If you wish to actively prevent slicing, there are a number of ways:



            Usually the most preferable way, unless you specifically need inheritance (you often don't) is to use encapsulation:



            #include <iostream>

            struct foo { int a; };
            struct bar
            {
            bar(int a, int b)
            : foo_(a)
            , b(b)
            {}

            int b;

            int get_a() const { return foo_.a; }

            private:
            foo foo_;
            };

            int main() {
            bar x{1,2};
            // foo y = x; // <- does not compile

            }


            Another more specialised way might be to alter the permissions around copy operators:



            #include <iostream>

            struct foo {
            int a;
            protected:
            foo(foo const&) = default;
            foo(foo&&) = default;
            foo& operator=(foo const&) = default;
            foo& operator=(foo&&) = default;

            };

            struct bar : foo
            {
            bar(int a, int b)
            : foo{a}, b{b}
            {}

            int b;
            };

            int main() {
            auto x = bar (1,2);
            // foo y = x; // <- does not compile
            }





            share|improve this answer




























              4














              Since 2011, the idiomatic way has been to use auto:



              #include <iostream>
              struct foo { int a; };
              struct bar : foo { int b; };

              int main() {
              bar x{1,2};
              auto y = x; // <- y is a bar
              }


              If you wish to actively prevent slicing, there are a number of ways:



              Usually the most preferable way, unless you specifically need inheritance (you often don't) is to use encapsulation:



              #include <iostream>

              struct foo { int a; };
              struct bar
              {
              bar(int a, int b)
              : foo_(a)
              , b(b)
              {}

              int b;

              int get_a() const { return foo_.a; }

              private:
              foo foo_;
              };

              int main() {
              bar x{1,2};
              // foo y = x; // <- does not compile

              }


              Another more specialised way might be to alter the permissions around copy operators:



              #include <iostream>

              struct foo {
              int a;
              protected:
              foo(foo const&) = default;
              foo(foo&&) = default;
              foo& operator=(foo const&) = default;
              foo& operator=(foo&&) = default;

              };

              struct bar : foo
              {
              bar(int a, int b)
              : foo{a}, b{b}
              {}

              int b;
              };

              int main() {
              auto x = bar (1,2);
              // foo y = x; // <- does not compile
              }





              share|improve this answer


























                4












                4








                4







                Since 2011, the idiomatic way has been to use auto:



                #include <iostream>
                struct foo { int a; };
                struct bar : foo { int b; };

                int main() {
                bar x{1,2};
                auto y = x; // <- y is a bar
                }


                If you wish to actively prevent slicing, there are a number of ways:



                Usually the most preferable way, unless you specifically need inheritance (you often don't) is to use encapsulation:



                #include <iostream>

                struct foo { int a; };
                struct bar
                {
                bar(int a, int b)
                : foo_(a)
                , b(b)
                {}

                int b;

                int get_a() const { return foo_.a; }

                private:
                foo foo_;
                };

                int main() {
                bar x{1,2};
                // foo y = x; // <- does not compile

                }


                Another more specialised way might be to alter the permissions around copy operators:



                #include <iostream>

                struct foo {
                int a;
                protected:
                foo(foo const&) = default;
                foo(foo&&) = default;
                foo& operator=(foo const&) = default;
                foo& operator=(foo&&) = default;

                };

                struct bar : foo
                {
                bar(int a, int b)
                : foo{a}, b{b}
                {}

                int b;
                };

                int main() {
                auto x = bar (1,2);
                // foo y = x; // <- does not compile
                }





                share|improve this answer













                Since 2011, the idiomatic way has been to use auto:



                #include <iostream>
                struct foo { int a; };
                struct bar : foo { int b; };

                int main() {
                bar x{1,2};
                auto y = x; // <- y is a bar
                }


                If you wish to actively prevent slicing, there are a number of ways:



                Usually the most preferable way, unless you specifically need inheritance (you often don't) is to use encapsulation:



                #include <iostream>

                struct foo { int a; };
                struct bar
                {
                bar(int a, int b)
                : foo_(a)
                , b(b)
                {}

                int b;

                int get_a() const { return foo_.a; }

                private:
                foo foo_;
                };

                int main() {
                bar x{1,2};
                // foo y = x; // <- does not compile

                }


                Another more specialised way might be to alter the permissions around copy operators:



                #include <iostream>

                struct foo {
                int a;
                protected:
                foo(foo const&) = default;
                foo(foo&&) = default;
                foo& operator=(foo const&) = default;
                foo& operator=(foo&&) = default;

                };

                struct bar : foo
                {
                bar(int a, int b)
                : foo{a}, b{b}
                {}

                int b;
                };

                int main() {
                auto x = bar (1,2);
                // foo y = x; // <- does not compile
                }






                share|improve this answer












                share|improve this answer



                share|improve this answer










                answered 8 hours ago









                Richard HodgesRichard Hodges

                57k658105




                57k658105























                    3














                    You can prevent the base from being copied outside of member functions of derived classes and the base itself by declaring the copy constructor protected:



                    struct foo {
                    // ...
                    protected:
                    foo(foo&) = default;
                    };





                    share|improve this answer



















                    • 4





                      but then I cannot copy foos anymore :( I'd like to prevent only copying a bar to a foo if possible

                      – user463035818
                      8 hours ago
















                    3














                    You can prevent the base from being copied outside of member functions of derived classes and the base itself by declaring the copy constructor protected:



                    struct foo {
                    // ...
                    protected:
                    foo(foo&) = default;
                    };





                    share|improve this answer



















                    • 4





                      but then I cannot copy foos anymore :( I'd like to prevent only copying a bar to a foo if possible

                      – user463035818
                      8 hours ago














                    3












                    3








                    3







                    You can prevent the base from being copied outside of member functions of derived classes and the base itself by declaring the copy constructor protected:



                    struct foo {
                    // ...
                    protected:
                    foo(foo&) = default;
                    };





                    share|improve this answer













                    You can prevent the base from being copied outside of member functions of derived classes and the base itself by declaring the copy constructor protected:



                    struct foo {
                    // ...
                    protected:
                    foo(foo&) = default;
                    };






                    share|improve this answer












                    share|improve this answer



                    share|improve this answer










                    answered 8 hours ago









                    eerorikaeerorika

                    89.8k664136




                    89.8k664136








                    • 4





                      but then I cannot copy foos anymore :( I'd like to prevent only copying a bar to a foo if possible

                      – user463035818
                      8 hours ago














                    • 4





                      but then I cannot copy foos anymore :( I'd like to prevent only copying a bar to a foo if possible

                      – user463035818
                      8 hours ago








                    4




                    4





                    but then I cannot copy foos anymore :( I'd like to prevent only copying a bar to a foo if possible

                    – user463035818
                    8 hours ago





                    but then I cannot copy foos anymore :( I'd like to prevent only copying a bar to a foo if possible

                    – user463035818
                    8 hours ago


















                    draft saved

                    draft discarded




















































                    Thanks for contributing an answer to Stack Overflow!


                    • Please be sure to answer the question. Provide details and share your research!

                    But avoid



                    • Asking for help, clarification, or responding to other answers.

                    • Making statements based on opinion; back them up with references or personal experience.


                    To learn more, see our tips on writing great answers.




                    draft saved


                    draft discarded














                    StackExchange.ready(
                    function () {
                    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55600025%2fidiomatic-way-to-prevent-slicing%23new-answer', 'question_page');
                    }
                    );

                    Post as a guest















                    Required, but never shown





















































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown

































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown







                    Popular posts from this blog

                    Masuk log Menu navigasi

                    Identifying “long and narrow” polygons in with PostGISlength and width of polygonWhy postgis st_overlaps reports Qgis' “avoid intersections” generated polygon as overlapping with others?Adjusting polygons to boundary and filling holesDrawing polygons with fixed area?How to remove spikes in Polygons with PostGISDeleting sliver polygons after difference operation in QGIS?Snapping boundaries in PostGISSplit polygon into parts adding attributes based on underlying polygon in QGISSplitting overlap between polygons and assign to nearest polygon using PostGIS?Expanding polygons and clipping at midpoint?Removing Intersection of Buffers in Same Layers

                    Старые Смолеговицы Содержание История | География | Демография | Достопримечательности | Примечания | НавигацияHGЯOLHGЯOL41 206 832 01641 606 406 141Административно-территориальное деление Ленинградской области«Переписная оброчная книга Водской пятины 1500 года», С. 793«Карта Ингерманландии: Ивангорода, Яма, Копорья, Нотеборга», по материалам 1676 г.«Генеральная карта провинции Ингерманландии» Э. Белинга и А. Андерсина, 1704 г., составлена по материалам 1678 г.«Географический чертёж над Ижорскою землей со своими городами» Адриана Шонбека 1705 г.Новая и достоверная всей Ингерманландии ланткарта. Грав. А. Ростовцев. СПб., 1727 г.Топографическая карта Санкт-Петербургской губернии. 5-и верстка. Шуберт. 1834 г.Описание Санкт-Петербургской губернии по уездам и станамСпецкарта западной части России Ф. Ф. Шуберта. 1844 г.Алфавитный список селений по уездам и станам С.-Петербургской губернииСписки населённых мест Российской Империи, составленные и издаваемые центральным статистическим комитетом министерства внутренних дел. XXXVII. Санкт-Петербургская губерния. По состоянию на 1862 год. СПб. 1864. С. 203Материалы по статистике народного хозяйства в С.-Петербургской губернии. Вып. IX. Частновладельческое хозяйство в Ямбургском уезде. СПб, 1888, С. 146, С. 2, 7, 54Положение о гербе муниципального образования Курское сельское поселениеСправочник истории административно-территориального деления Ленинградской области.Топографическая карта Ленинградской области, квадрат О-35-23-В (Хотыницы), 1930 г.АрхивированоАдминистративно-территориальное деление Ленинградской области. — Л., 1933, С. 27, 198АрхивированоАдминистративно-экономический справочник по Ленинградской области. — Л., 1936, с. 219АрхивированоАдминистративно-территориальное деление Ленинградской области. — Л., 1966, с. 175АрхивированоАдминистративно-территориальное деление Ленинградской области. — Лениздат, 1973, С. 180АрхивированоАдминистративно-территориальное деление Ленинградской области. — Лениздат, 1990, ISBN 5-289-00612-5, С. 38АрхивированоАдминистративно-территориальное деление Ленинградской области. — СПб., 2007, с. 60АрхивированоКоряков Юрий База данных «Этно-языковой состав населённых пунктов России». Ленинградская область.Административно-территориальное деление Ленинградской области. — СПб, 1997, ISBN 5-86153-055-6, С. 41АрхивированоКультовый комплекс Старые Смолеговицы // Электронная энциклопедия ЭрмитажаПроблемы выявления, изучения и сохранения культовых комплексов с каменными крестами: по материалам работ 2016-2017 гг. в Ленинградской области