Strange "->* []" expression in C++ source code of cpp.react library

The only example on the linked page where I see ->* is this:

auto in = D::MakeVar(0);

auto op1 = in ->* [] (int in)
{
    int result = in /* Costly operation #1 */;
    return result;
};

auto op2 = in ->* [] (int in)
{
    int result = in /* Costly operation #2 */;
    return result;
};

Here's my guess - whatever type is returned by D::MakeVar() overloads the pointer-to-member operator ->*, and the second argument for that overloaded operator is a function object, i.e. the lambda expression.

As for this example:

auto volume = (width,height,depth) ->* [] (int w, int h, int d) {
    return w * h * d;
};

I'm guessing whatever types width, height & depth are, overload the comma operator, and the result yields the same type as what MakeVar yields, or another type that overloads ->*. The rest is the same as the first example.


@Praetorian's answer is correct. This is code from cpp.react

///////////////////////////////////////////////////////////////////////////////////////////////////
/// operator->* overload to connect inputs to a function and return the resulting node.
///////////////////////////////////////////////////////////////////////////////////////////////////
// Single input
template
<
    typename D,
    typename F,
    template <typename D_, typename V_> class TSignal,
    typename TValue,
    class = std::enable_if<
        IsSignal<TSignal<D,TValue>>::value>::type
>
auto operator->*(const TSignal<D,TValue>& inputNode, F&& func)
    -> Signal<D, typename std::result_of<F(TValue)>::type>
{
    return D::MakeSignal(std::forward<F>(func), inputNode);
}

// Multiple inputs
template
<
    typename D,
    typename F,
    typename ... TSignals
>
auto operator->*(const InputPack<D,TSignals ...>& inputPack, F&& func)
    -> Signal<D, typename std::result_of<F(TSignals ...)>::type>
{
    return apply(
        REACT_IMPL::ApplyHelper<D, F&&, TSignals ...>::MakeSignal,
        std::tuple_cat(std::forward_as_tuple(std::forward<F>(func)), inputPack.Data));
}

///////////////////////////////////////////////////////////////////////////////////////////////////
/// Comma operator overload to create input pack from 2 signals.
///////////////////////////////////////////////////////////////////////////////////////////////////
template
<
    typename D,
    typename TLeftVal,
    typename TRightVal
>
auto operator,(const Signal<D,TLeftVal>& a, const Signal<D,TRightVal>& b)
    -> InputPack<D,TLeftVal, TRightVal>
{
    return InputPack<D, TLeftVal, TRightVal>(a, b);
}

///////////////////////////////////////////////////////////////////////////////////////////////////
/// Comma operator overload to append node to existing input pack.
///////////////////////////////////////////////////////////////////////////////////////////////////
template
<
    typename D,
    typename ... TCurValues,
    typename TAppendValue
>
auto operator,(const InputPack<D, TCurValues ...>& cur, const Signal<D,TAppendValue>& append)
    -> InputPack<D,TCurValues ... , TAppendValue>
{
    return InputPack<D, TCurValues ... , TAppendValue>(cur, append);
}

as you can see it overloaded free function operator->* which takes a signal (D::MakeVar(0)) and a functor (lambda)

and free function operator, which takes two signal


(Author here)

First of all, Praetorians answer is correct, but I'd like to elaborate a bit.

Note that this library is still very experimental and I'm still working on the documentation. The current state of said documentation can be found in the wiki, in particular https://github.com/schlangster/cpp.react/wiki/User-Guide-%7C-Signals is related to the question.

Here's a more verbose example:

int calcVolume(int w, int h, int d) { return w*h*d; }

D::VarSignalT<int> width  = D::MakeVar(1);
D::VarSignalT<int> height = D::MakeVar(2);
D::VarSignalT<int> depth  = D::MakeVar(3);

D::SignalT<int> volume    = MakeSignal(&calcVolume, width, height, depth);

Observe(volume, [] (int v) {
    printf("volume changed to %d\n", v);
});

width.Set(10); // => volume changed to 60.

printf("volume: %d\n", volume.Value()); // short: volume()

It's sort of a bind (bind signals as function input), but it's NOT the same as a reverse std::bind. volume is not a function object. In particular, volume is not recalculated when you call Value(), it is recalculated when one of its dependent signals changes, the result is saved, and Value() returns it. So it's essentially push based change propagation with some extra features (no redundant updates, no glitches, optional implicit parallelization).

The problem is that MakeSignal gets confusing when mixed with temporary signals and lambdas:

// First create a temporary area signal, then use it as an argument for the volume signal
D::SignalT<int> volume  = MakeSignal(
    [] (int a, int d) { return a * d; },
    MakeSignal(
        [] (int w, int h) { return w * h; },
        width, height),
    depth);

Nobody wants to read stuff like that, right? At least I don't want to.

So there's an alternative syntax that moves the dependencies to the left, wrapped by SignalList.

// Note: Not sure if I have already pushed this variant yet
D::SignalT<int> volume =
    MakeSignalList(
        MakeSignalList(width, height).Bind([] (int w, int h) { return w * h; }),
        depth
    ).Bind([] (int a, int d) { return a * d; });

And, finally, with the evil comma and ->* overloads:

D::SignalT<int> volume =
(
    (width, height) ->* [] (int w, int h) { return w * h; },
    depth
)
->* [] (int area, int d) { return a * d; };

The problem with this, as others have noted, is that anyone seeing it for the first time doesn't know what the heck is going on.

On the other hand, connecting signals to functions should be a very common task when using this library. Once you know what it does, the ->* version is more concise and it visualizes the dataflow graph (edges from width and height to the temporary area, edges from area and depth to volume).