19

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

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 idiomatic way to write foo such that one cannot slice instances of any derived class?

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185

3 Answers3

22

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.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • Note that this doesn't prevent explicit slicing like this: `foo y = static_cast(x);`. That said, perhaps it's not a problem to OP. – eerorika Apr 09 '19 at 20:02
  • if I understand correctly this is a nice way to prevent implicit conversions for function parameters in general – 463035818_is_not_an_ai Apr 09 '19 at 20:05
  • 1
    @user463035818 [Yep](https://stackoverflow.com/questions/32716589/force-function-to-be-called-only-with-specific-types). I've been using it since I've asked that Q. – NathanOliver Apr 09 '19 at 20:06
  • 5
    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 Apr 09 '19 at 20:08
  • actually I was a bit hestitant to accept this answer. The technique is great, but in fact it opens the door to specializing all kinds of unwanted assignments, though if I have to choose between the javaish "protect against every possible stupidity at any cost" vs a pythonic "we are all adults" then I know what to pick ;) – 463035818_is_not_an_ai Apr 10 '19 at 08:23
  • @eerorika it is even worse than that, now anybody can provide a specialization for copying and assigning `foo` and do all kinds of stupid stuff, though I still like the idea – 463035818_is_not_an_ai Apr 10 '19 at 08:24
6

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
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
2

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;
};
eerorika
  • 232,697
  • 12
  • 197
  • 326
  • 7
    but then I cannot copy `foo`s anymore :( I'd like to prevent only copying a bar to a foo if possible – 463035818_is_not_an_ai Apr 09 '19 at 19:42
  • Not the answer you want, but if you follow [Item 33: Make non-leaf classes abstract](http://ptgmedia.pearsoncmg.com/imprint_downloads/informit/aw/meyerscddemo/demo/mec/M_FR.HTM) from Meyers' More Effective C++, then you'll find this never arises. I find this guideline incredibly helpful in designing clean class hierarchies, though I understand it can take some getting used to. – John McFarlane May 19 '23 at 11:56