Search code examples
c++c++17initializer-listfold-expressionparameter-pack

Initializer list, parameter pack expansion, fold expressions and order of evaluation


I have the following code to address a n-dimensional tensor class (offset is a std::vector of std::size_t):

template <typename ...Ts>
double Tensor::at(int first, Ts... others) {
    int i = 0;
    std::size_t index = static_cast<std::size_t>(first)*_offset[0];
    (void)std::initializer_list<int>{(index += _offset[++i]*static_cast<std::size_t>(others), 0)...};
    return _data[index];
}

This works but I have some questions since I wrote this code by piecing lots of information I found online.

  1. The first one is what exactly is happening at line 5 and the correct name for it. If I am not wrong this should unpack the expression and create a braced-init-list[*]. Each element of this list is itself a comma-separated list in the form (exp,0)[**]. The expansion should therefore be {(exp1,0),...(expn,0)}. Am I right?

  2. The second one is about the evaluation order. [**] should be only useful to provide a return value to the constructor of the inizialier_list and not serve other purposes (even if index could be itself the return value?). [*] instead, gives an order to the evaluation of the expression. I found it in the example of a print function:

(void)std::initializer_list<int>{(print(others),0)...}; 

so that print is called in order on the parameters. Is this also true for composed expressions?:

(void)std::initializer_list<int>{(print(compute(others)),0)...}; 

so that if "compute" depends on a state, this is updated according to the order of the parameters leading to always the same results (independently of compiler ecc...). In the tensor example this refers to the ++i.

  1. The last question refers to this , in which the use of += in the way did for the tensors is called fold expression. I don't think this is the case. Am I wrong?

Solution

  • 1. Your understanding is correct.

    2. I'm not sure what you mean by "composed expressions", etc, but yes, initializers in braces are always evaluated left-to-right. See cppreference:

    1. In list-initialization, every value computation and side effect of a given initializer clause is sequenced before every value computation and side effect associated with any initializer clause that follows it in the brace-enclosed comma-separated list of initalizers.

    3. This is not a fold expression. A fold expressions must have one of the following forms:

    • ( pack op ... )
    • ( ... op pack )
    • ( pack op ... op init )
    • ( init op ... op pack )

    Where op is a binary operator (most of them are allowed), pack is an expression containing at least one unexpanded parameter pack (Ts or others in your case), init is just a normal expression, and ... must be present literally.

    Your function can be simplified using a fold expression. Instead of:

    (void)std::initializer_list<int>{(index += _offset[++i]*static_cast<std::size_t>(others), 0)...};
    

    you can write:

    ((index += _offset[++i]*static_cast<std::size_t>(others)), ...);