Initializing structs in C++

后端 未结 5 902
余生分开走
余生分开走 2020-12-30 21:19

As an addendum to this question, what is going on here:

#include 
using namespace std;

struct A {
    string s;
};

int main() {
    A a = {0}         


        
相关标签:
5条回答
  • 2020-12-30 21:59

    As people have pointed out, this "works" because string has a constructor that can take 0 as a parameter. If we say:

    #include <map>
    using namespace std;
    
    struct A {
        map <int,int> m;
    };
    
    int main() {
        A a = {0};
    }
    

    then we get a compilation error, as the map class does not have such a constructor.

    0 讨论(0)
  • 2020-12-30 22:02

    Your struct is an aggregate, so the ordinary rules for aggregate initialization work for it. The process is described in 8.5.1. Basically the whole 8.5.1 is dedicated to it, so I don't see the reason to copy the whole thing here. The general idea is virtually the same it was in C, just adapted to C++: you take an initializer from the right, you take a member from the left and you initialize the member with that initializer. According to 8.5/12, this shall be a copy-initialization.

    When you do

    A a = { 0 };
    

    you are basically copy-initializing a.s with 0, i.e. for a.s it is semantically equivalent to

    string s = 0;
    

    The above compiles because std::string is convertible from a const char * pointer. (And it is undefined behavior, since null pointer is not a valid argument in this case.)

    Your 42 version will not compile for the very same reason the

    string s = 42;
    

    will not compile. 42 is not a null pointer constant, and std::string has no means for conversion from int type.

    P.S. Just in case: note that the definition of aggregate in C++ is not recursive (as opposed to the definition of POD, for example). std::string is not an aggregate, but it doesn't change anything for your A. A is still an aggregate.

    0 讨论(0)
  • 2020-12-30 22:02

    0 is a null pointer constant

    S.4.9:

    A null pointer constant is an integral constant expression (5.19) rvalue of integer type that evaluates to zero.

    A null pointer constant can be converted to any other pointer type:

    S.4.9:

    A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type

    What you gave for the definition of A is considered an aggregate:

    S.8.5.1:

    An aggregate is an array or a class with no user-declared constructors, no private or protected non-static data members, no base classes, and no virtual functions.

    You are specifying an initializer clause:

    S.8.5.1:

    When an aggregate is initialized the initializer can contain an initializer-clause consisting of a brace enclosed, comma-separated list of initializer-clauses for the members of the aggregate

    A contains a member of the aggregate of type std::string, and the initializer clause applies to it.

    Your aggregate is copy-initialized

    When an aggregate (whether class or array) contains members of class type and is initialized by a brace enclosed initializer-list, each such member is copy-initialized.

    Copy initializing means that you have the equivalent to std::string s = 0 or std::string s = 42;

    S.8.5-12

    The initialization that occurs in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and brace-enclosed initializer lists (8.5.1) is called copy-initialization and is equivalent to the form T x = a;

    std::string s = 42 will not compile because there is no implicit conversion, std::string s = 0 will compile (because an implicit conversion exists) but results in undefined behavior.

    std::string's constructor for const char* is not defined as explicit which means you can do this: std::string s = 0

    Just to show that things are actually being copy-initialized, you could do this simple test:

    class mystring
    {
    public:
    
      explicit mystring(const char* p){}
    };
    
    struct A {
      mystring s;
    };
    
    
    int main()
    {
        //Won't compile because no implicit conversion exists from const char*
        //But simply take off explicit above and everything compiles fine.
        A a = {0};
        return 0;
    }
    
    0 讨论(0)
  • 2020-12-30 22:02

    In 21.3.1/9 the standard forbids the char* argument of the relevant constructor of a std::basic_string from being a null pointer. This should throw a std::logic_error, but I have yet to see where in the standard is the guarantee that violating a precondition throws a std::logic_error.

    0 讨论(0)
  • 2020-12-30 22:06

    8.5.1/12 "Aggregates" says:

    All implicit type conversions (clause 4) are considered when initializing the aggregate member with an initializer from an initializer-list.

    So

    A a = {0};
    

    will get initialized with a NULL char* (as AndreyT and Johannes indicated), and

    A a = {42};
    

    will fail at compile time since there's no implicit conversion that'll match up with a std::string constructor.

    0 讨论(0)
提交回复
热议问题