56

Here is a C++ snippet that I found in the documentation of the cpp.react library:

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

I have never seen the ->* [] notation. First, I thought that it was just a typo, but I also found such an expression in the source code:

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

Is this valid C++11 (or C++14)? What does it mean?

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
Philipp Claßen
  • 41,306
  • 31
  • 146
  • 239
  • Wild guess; the arrow is for auto deduced return type of C++14, and though superflurios (for lambdas) allowed. This is just my own speculation however! – Skeen May 12 '14 at 21:31
  • 19
    This is clearly one of the reasons why I do not necessarily use the latest syntax stuff. You can't even google that. – Thomas Weller May 12 '14 at 21:34
  • 2
    @Skeen: I'm pretty sure the arrow for deduced return types is always to the right of the parameter list, I almost think `(width,height,depth)` is abusing the comma operator, using `->` to get a pointer, which is `*` with a lambda... – Mooing Duck May 12 '14 at 21:35
  • it looks like a lambda http://en.cppreference.com/w/cpp/language/lambda – kaman May 12 '14 at 21:36
  • 3
    member operator plus lambda? http://stackoverflow.com/questions/1779685/what-is-operator-in-c – Thomas Weller May 12 '14 at 21:39
  • 19
    If no-one on the internet can read it, it's clearly a bad idea to write it. – Richard Hodges May 12 '14 at 21:40
  • 1
    Oh, `->*` is a pointer to a member dereference! I'd forgotten! – Mooing Duck May 12 '14 at 21:41
  • I think before we can make any sense of this we need to see the return type of D::MakeVar(int) – Richard Hodges May 12 '14 at 21:49
  • Wow. This is the kind of stuff that makes me wonder why I ever switched from Fortran, that maybe I should switch back to Fortran. – David Hammen May 12 '14 at 21:55
  • 1
    Classic abuse of operator overloading. Only overload the operator if the meaning can readily be deduced. While this seems syntactically clever, 98% of programmers cannot understand it. – Paul Renton May 12 '14 at 21:59
  • I speculate that `width`,`height`, and `depth` are bound parameters. The comma operator binds them togeather, and the `->*` applies to the function. Sort of like a reverse `std::bind` – Mooing Duck May 12 '14 at 22:02
  • @RichardHodges Not so easy, as D is defined with a macro. I think I've tracked the definition down to this line, but I'm not 100% sure: https://github.com/schlangster/cpp.react/blob/master/include/react/Signal.h#L286 Then I loose track, but there is a location which overloads "operator,": https://github.com/schlangster/cpp.react/blob/master/include/react/Signal.h#L705 – Philipp Claßen May 12 '14 at 22:07
  • 1
    Add "arrow star" operator somewhere in the question, to aid future googles? – Yakk - Adam Nevraumont May 13 '14 at 01:15

3 Answers3

42

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
  • 106,671
  • 19
  • 240
  • 328
  • Since the right hand side is not an object with members to dereference, `operator->*` must have been overloaded: http://coliru.stacked-crooked.com/a/d73916c5318871f7 – Mooing Duck May 12 '14 at 21:45
  • Also relevant: [Are free operator->* overloads evil?](https://stackoverflow.com/questions/2696864/are-free-operator-overloads-evil). – Praetorian May 12 '14 at 21:48
  • 32
    that's sooo messed up. – Anycorn May 12 '14 at 21:48
  • 1
    Better implementation: http://coliru.stacked-crooked.com/a/a75b809103e6060c My guess is that `MakeVar` is a bound list of parameters, and then it's applied to a function via `operator->*()` – Mooing Duck May 12 '14 at 22:01
  • Wow, your instincts were 100% right. After using the language for several years, I have never seens the "->*" operator, nor would I have tought about the comma operator (instead I guessed it was maybe C++11's "alternative function syntax"). Well, that is definitely not what you learn from reading Scott Meyers books. :-) – Philipp Claßen May 12 '14 at 22:20
  • 1
    @PhilippClaßen To be honest, I wasn't sure what to call `->*` either and tried googling *pointer to member operator*. As luck would have it, that search turned up the weird little `->*` operator, and the linked questions. – Praetorian May 12 '14 at 22:23
  • @PhilippClaßen One could probably argue that the kind of person who overloads `->*` this way is not the kind of person who learnt C++ from Scott Meyer's books... – The Vee Dec 02 '16 at 13:55
17

@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

Community
  • 1
  • 1
Bryan Chen
  • 45,816
  • 18
  • 112
  • 143
10

(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).

user3641432
  • 101
  • 3
  • Thank you for the answer. As you are building a DSL, I can understand that your priority is to make the code more readable from the user perspective. It is just that I was not aware of the "->*" operator in C++. Interesting example though. I haven't yet overloaded the comma or "->*" operator, but I haven't written a DSL either. I directly went to the code because I was curious how it is implemented. Initially, I thought that it is using a new C++14 feature that I haven't heard of. :-) – Philipp Claßen May 15 '14 at 17:37
  • If it were me using your library, I'd want a separate definition line for each stage in the signal chain. Otherwise, for me (and I suspect many others) the code is unmaintainable. I appreciate that it's second nature for you but your perspective is unique since you wrote the code. Overloading operators in a way that changes their meaning is widely regarded as evil as I am sure you know. In the given example I see no advantage in chaining the signals during definition other than syntactic cleverness. – Richard Hodges May 16 '14 at 20:20
  • Thanks for the feedback. I had given it some thought myself and ultimately thought using a DSL was a good choice here, but I also wanted to wait for opinions of others on the matter. – user3641432 May 17 '14 at 00:12
  • FYI, I redesigned the API a bit and got rid of the ->*, so hopefully it's more intuitive now. – user3641432 May 23 '14 at 15:25
  • 2
    May I suggest replacing `->*` with a named operator? `::SignalT volume = ( (width, height) *connect_to* [] (int w, int h) { return w * h; }, depth ) *connect_to* [] (int area, int d) { return a * d; };` -- while magical, it may be less confusing than `->*`. You can even `*connect_with*` instead of `,`. (the named operator is a tag type with left hand `operator*` overloaded, which returns a bundle that has right hand `operator*` overloaded, which invokes the actual "operation" function. I often store the operation function within the tag type for locality reasons.) – Yakk - Adam Nevraumont Aug 07 '14 at 19:56