-1

The following code compiles

#include <vector>
struct Foo
{
    int a;
    int b;
};

int main()
{
    std::vector<Foo> foos;
    foos.emplace_back(Foo{1, 2});
}

However, it fails if I replace struct with class1. Aside from doing that (I would like to keep the members private), is there something I can do to enable me to use the Foo{1, 2} notation? I'm keen to avoid writing a constructor for Foo myself.

I'm using C++17 although of course will be interested in general C++ answers.


1 Compile error (MSVC) is

1>c:\...: error C2440: 'initializing': cannot convert from 'initializer list' to 'Foo'
1>c:\...: note: No constructor could take the source type, or constructor overload resolution was ambiguous
1>c:\...: error C2672: 'std::vector<Foo,std::allocator<_Ty>>::emplace_back': no matching overloaded function found
1>        with
1>        [
1>            _Ty=Foo
1>        ]
vvv444
  • 2,764
  • 1
  • 14
  • 25
P45 Imminent
  • 8,319
  • 4
  • 35
  • 78
  • 1
    Remember that `struct` and `class` differ in several *significant* ways, one of which is the visibility of properties, so to keep them private, create a constructor. Some things can't be avoided, even if you're keen. – tadman May 24 '23 at 16:31
  • 1
    the point of being private is that you cannot access them from outside. – 463035818_is_not_an_ai May 24 '23 at 16:33
  • @tadman: So are you saying I need to create a constructor? I can't use the `{}` syntax out of the box? – P45 Imminent May 24 '23 at 16:33
  • @463035818_is_not_a_number: Indeed but surely at the point of construction I might be able to? – P45 Imminent May 24 '23 at 16:33
  • This is how encapsulation works. You can't just flaunt the rules because you feel like they don't matter. If you want private or protected properties, you need a constructor to bridge that divide. It's the constructor's job to decide what properties can be accessed, and *how*. If you feel like this is all just too much, you can just use `struct` which is much more laisez-faire. – tadman May 24 '23 at 16:34
  • Perhaps I need to create an explicit constructor for a 'initializer list' to 'Foo' – P45 Imminent May 24 '23 at 16:34
  • 1
    no also not for initialization, thats not specific to `{}` – 463035818_is_not_an_ai May 24 '23 at 16:34
  • btw the question is easier to read and understand when you post the code that has the error and the error, rather than some code and instruction on what has to be changed to get the error. The difference between `struct` and `class` btw is only that: the default access of members (and default inheritance for base classes), otherwise they both define a class type – 463035818_is_not_an_ai May 24 '23 at 16:36
  • Keep in mind the reason for `protected` and `private` properties is to prevent coupling with external code, like you have here with your initialization call. The whole *point* of them is to make sure access is controlled. A `struct` is just "dumb data" and has no such restrictions by default. – tadman May 24 '23 at 16:36
  • "I'm keen to avoid writing a constructor for Foo myself.". Why? Design choices affect not only code design or workflow, but also how hard one must work for simple tasks (i.e. initialization, instantiation, assignment or even just passing something as a param). Sometimes one thing you don't want because of silly, personal or moody reasons might be the thing that saves you from wasted hours not only in implementation, but also debugging. Also keep in mind, if this is for work, chances are others will work on it too, so think of others too, not just what you are not "keen" on doing. – TheNomad May 24 '23 at 16:37
  • Write a constructor. The reason your current example compiles is because you're using aggregate initialization and that requires members to be public. – digito_evo May 24 '23 at 16:37
  • I recommend to avoid that syntax *entirely*, and even more in templates, it is flawed anyway. You might have a class `C` providing a constructor with e.g. two integers doing something specific. You construct objects of via the syntax: `C c{10, 12};` – all fine so far. But then a new version of `C` is provided now with an additional constructor added accepting a `std::initializer_list`. All code previously fine is now broken for calling the wrong constructor! If you had `C c(10, 12);` instead the code remains compatible, and `C c({12, 10});` safely calls the new constructor... – Aconcagua May 24 '23 at 16:50
  • *I would like to keep the members private* Make it a `class` and provide a public constructor. *I'm keen to avoid writing a constructor* Keep it a `struct` with public member variables. – Eljay May 24 '23 at 17:26

2 Answers2

2

The feature you are using here is called aggregate initialization. By definition of aggregate from the C++17 standard [dcl.init.aggr/1.2]:

An aggregate is an array or a class with
(1.1) no user-provided, explicit, or inherited constructors
(1.2) no private or protected non-static data members
...

Thus, it's just impossible to do what you are asking for without implementing a constructor. The reasoning behind this is simple - if class/struct defines something to be private, it shouldn't be accessible from outside in any way. Otherwise, it contradicts the concept of encapsulation.

Please note that the curly braces themselves don't necessarily mean aggregate initialization. Foo{a, b} is a valid syntax for calling a constructor, for example:

class Foo
{
    int a;
    int b;

public:
    Foo(int a_, int b_) : a{a_}, b{b_} {}
};

Foo f{1, 2}; // Calls constructor here
vvv444
  • 2,764
  • 1
  • 14
  • 25
1

private means its private. Its an implementation detail. A user does not have to care about it. You can change or remove private members and a user of the class will not notice, their code will continue to compile without errors as long as the public interface is not touched.

Write a constructor:

struct Foo
{
    Foo(int a,int b) : a(a),b(b) {}
    private:
    int a;
    int b;
};

To illustrate my point, consider later you change the class to

struct Foo
{
    Foo(int a,int b) : ab(a+b) {}
    private:
    int ab;
};

Different ranges of input leading to overflow and different sizeof aside, a user will not notice the difference. And thats good, because only the private part was modified.

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