Search code examples
c++jsonboost

brace-inititialisation of boost::json::value converts it from an object to an array


In the below example I parse a json object using boost::json.

When I print the type of the returned boost::json::value it is of type object, as expected.

I then have 2 classes which are identical in every way, other than in BraceInit I initialise my member boost::json::value using brace initialisation and in ParenInit I initialise my member boost::json::value using parens.

Using brace initialisation causes my object to be converted into an array, of size 1, containing my original object.

#include <iostream>
#include <boost/json.hpp>

namespace json = boost::json;

void print_type(const json::value& jv)
{
    switch (jv.kind())
    {
        case json::kind::object: std::cout << "object\n"; break;
        case json::kind::array: std::cout << "array\n"; break;
        default: std::cout << "other\n"; break;
    }
};

struct BraceInit
{
    BraceInit(json::value jv)
        : _jv{jv}
    {
        print_type(_jv);
    }
    json::value _jv;
};

struct ParenInit
{
    ParenInit(json::value jv)
        : _jv(jv)
    {
        print_type(_jv);
    }

    json::value _jv;
};

int main()
{
    const std::string str = R"({ "foo": "bar" })";
    const json::value jv = json::parse(str);
    print_type(jv);

    BraceInit b(jv);
    ParenInit p(jv);
    return 0;
}

Output:

object
array
object

What is happening here?

Is my "brace initialisation" actually not doing brace initialisation as I expect, but rather creating an std::initializer_list of size 1?


Solution

  • From [class.base.init]/7

    The expression-list or braced-init-list in a mem-initializer is used to initialize the designated subobject (or, in the case of a delegating constructor, the complete class object) according to the initialization rules of 11.6 for direct-initialization.

    11.6 here refers to [dcl.init], which, under paragraph 17 states:

    The semantics of initializers are as follows. The destination type is the type of the object or reference being initialized and the source type is the type of the initializer expression. If the initializer is not a single (possibly parenthesized) expression, the source type is not defined.

    (17.1) — If the initializer is a (non-parenthesized) braced-init-list or is = braced-init-list, the object or reference is list-initialized (11.6.4).

    11.6.4 simply refers to [dcl.init.list].

    So, to me, it sounds like _jv{jv} calls the initialization-list version of the constructor (if it exists), which, according to the boost documentation, produces an array.

    Note: all items from C++17 draft N4659

    You can see this in a simplified version here: https://godbolt.org/z/9qrf3EooY


    Edit: cppreference simplifies this to:

    A std::initializer_list object is automatically constructed when:

    a braced-init-list is used to list-initialize an object, where the corresponding constructor accepts an std::initializer_list parameter