future_cxx icon indicating copy to clipboard operation
future_cxx copied to clipboard

p1214 - cAll the Things! - motivation, numbers, examples for C++29

Open Morwenn opened this issue 6 years ago • 12 comments

So, since P1214 isn't likely to move before we can present actual numbers from an actual implementation in a compiler, I'm opening this issue to gather all the feedback & numbers I can provide. That way when it may have chances to pass EWGI and not to die another sad death before EWG.

No need for the bloated <functional>

(this section is probably less impactful now that we have modules)

One thing that the paper does not mention is that <functional> is often included for std::invoke only [citation needed] and that <functional> is known for being one of the slowest headers to parse:

  • https://blog.magnum.graphics/backstage/lightweight-stl-compatible-unique-pointer/
  • https://blog.magnum.graphics/announcements/2019.01/ - « There’s still a lot do in the Mag­num code­base and while re­mov­ing the de­pen­den­cy on <memory> and <functional> is cur­rent­ly not re­sult­ing in any sig­nif­i­cant com­pile-time speed up when build­ing Mag­num it­self, projects us­ing Mag­num re­port­ed com­pile times be­ing short­er by 20–30% as a re­sult of this change. »
  • https://drive.google.com/file/d/1A1M-Ezy4ZaphhWY-ooPUSQb_c9IzL2XR/view
  • https://raw.githubusercontent.com/ned14/stl-header-heft/master/graphs/msvs-2017.png
  • https://slides.com/onqtam/faster_builds/#/13
  • https://github.com/s9w/cpp-lit (MSVC)
  • https://old.reddit.com/r/cpp/comments/ik5inf/standard_library_include_times_measured_in_msvc/g3luwmn/ - Peter Dimov: « <functional>, single-handedly responsible for killing compile times across the world. »

Modules might change this, but the world probably won't fully move to modules until build systems and IDEs are ready to handle them.

std::invoke is comparatively slow to compile

According to the benchmarks of Eggs.Invoke, std::invoke is 1.5 to 18 times slower to compile than an equivalent plain function call, and the compilation process uses up to 12 times as much memory. It is quite underwhelming for such a small fundamental function.

Better error messages

The feature isn't implemented yet in compilers, but using normal functions as examples should give an example of how error messages can be improved (is there a simple way to make Tony Tables GitHub issues?):

#include <functional>

void func(int) {}

int main()
{
    struct foo_t {} foo;
    std::invoke(func, foo);
}

This snippet gives the following output on the Compiler Explorer with GCC 8.2 :

<source>: In function 'int main()':
<source>:8:26: error: no matching function for call to 'invoke(void (&)(int), main()::foo_t&)'
     std::invoke(func, foo);
                          ^
In file included from <source>:1:
/opt/compiler-explorer/gcc-8.2.0/include/c++/8.2.0/functional:78:5: note: candidate: 'template<class _Callable, class ... _Args> std::invoke_result_t<_Callable, _Args ...> std::invoke(_Callable&&, _Args&& ...)'
     invoke(_Callable&& __fn, _Args&&... __args)
     ^~~~~~
/opt/compiler-explorer/gcc-8.2.0/include/c++/8.2.0/functional:78:5: note:   template argument deduction/substitution failed:

With Clang 7:

<source>:8:5: error: no matching function for call to 'invoke'
    std::invoke(func, foo);
    ^~~~~~~~~~~
/opt/compiler-explorer/gcc-8.2.0/lib/gcc/x86_64-linux-gnu/8.2.0/../../../../include/c++/8.2.0/functional:78:5: note: candidate template ignored: substitution failure [with _Callable = void (&)(int), _Args = <foo_t &>]: no type named 'type' in 'std::invoke_result<void (&)(int), foo_t &>'
    invoke(_Callable&& __fn, _Args&&... __args)
    ^
1 error generated.

And with MSVC 19.16:

<source>(8): error C2672: 'std::invoke': no matching overloaded function found
<source>(8): error C2893: Failed to specialize function template 'unknown-type std::invoke(_Callable &&,_Types &&...) noexcept(<expr>)'
<source>(8): note: With the following template arguments:
<source>(8): note: '_Callable=void (__cdecl &)(int)'
<source>(8): note: '_Types={main::foo_t &}'

Now take the same snippet but without std::invoke:

void func(int) {}

int main()
{
    struct foo_t {} foo;
    func(foo);
}

Here is the error message with GCC 8.2:

<source>: In function 'int main()':
<source>:6:10: error: cannot convert 'main()::foo_t' to 'int'
     func(foo);
          ^~~
<source>:1:11: note:   initializing argument 1 of 'void func(int)'
 void func(int) {}
           ^~~

With Clang 7:

<source>:6:5: error: no matching function for call to 'func'
    func(foo);
    ^~~~
<source>:1:6: note: candidate function not viable: no known conversion from 'struct foo_t' to 'int' for 1st argument
void func(int) {}
     ^

And with MSVC 19.16:

<source>(6): error C2664: 'void func(int)': cannot convert argument 1 from 'main::foo_t' to 'int'
<source>(6): note: No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called

It should give a good idea of how the proposal should be able to improve error messages. It is worth noting that no compiler actually points to the definition of func on failure when std::invoke is used.

A better debugging experience

Some areas such as the video game industry have a need for interactive debugging and fast debug builds. Release builds tend to collapse calls to std::invoke into the appropriate underlying function call, but debug builds need to retain information about std::invoke and its helper functions.

Having language support for callables removes the aforementioned unneeded extra debug information - and hence can help reducing the size of the debug binary -, but also allows for a smoother navigation through function calls when debugging. In the current state of things, debugger implementers can "ignore" std::invoke as some already ignore std::move and std::forward and jump into std::function::operator() for the sake of a smoother debug navigation, but having language support would give the same experience without requiring implementers to tweak their debuggers.

Once generic libraries start moving away from std::invoke to generic language callables, it might drive their adoption in the areas that require a smoother debugging experience.

Links about issues with the debugging experience:

  • http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1832r0.html
  • https://aras-p.info/blog/2018/12/28/Modern-C-Lamentations/

There might be more special cases in the future

In P0847R2, it is mentioned that the new kinds of member functions proposed don't follow the usual rules of member function pointers and give the following example:

struct Y {
    int f(int, int) const&;
    int g(this Y const&, int, int);
};

Y y;
y.f(1, 2); // ok as usual
y.g(3, 4); // ok, this paper

auto pf = &Y::f;
pf(y, 1, 2);              // error: pointers to member functions are not callable
(y.*pf)(1, 2);            // okay, same as above
std::invoke(pf, y, 1, 2); // ok

auto pg = &Y::g;
pg(y, 3, 4);              // okay, same as above
(y.*pg)(3, 4);            // error: pg is not a pointer to member function
std::invoke(pg, y, 3, 4); // ok

This means that depending on how you declare your member function, you might get different results on how it is possible to invoke a pointer to it. I think that it's yet another case where P1214 makes things better overall. It might be something worth mentioning in P0847 if the authors are fine with it.

Related proposals

P2826 mentions that this proposal could be useful to pointer-to-member-functions directly for their proposed replacement function feature.

It makes library implementers life easier

Once library implementers (including standard library ones) start relying on this feature, it will free there time to implement you favourite features faster! :D

(don't include this one in the paper)

Morwenn avatar Jan 17 '19 20:01 Morwenn

Got you some examples of error messages that show how things might be improved :)

Morwenn avatar Jan 18 '19 18:01 Morwenn

Thanks, I'll try to Tony Table them.

Note I don't think I will submit this paper for Pre-Kona? Since it doesn't seem like it's worth adding to the mailing list pile to churn. (Maybe by the time Cologne or Belfast roll around, or the meeting after that?) This will certainly help when I start working on Dang (Derpstorm Clang, haaaah).

ThePhD avatar Jan 18 '19 18:01 ThePhD

I don't think it's worth updating it before you intend to present bring it again before EWGI, unless you want to gather more feedback from other places before that :p

Also great pun, I love it ❤️

Morwenn avatar Jan 18 '19 19:01 Morwenn

Deducing this and its rules for member function pointer just gave us some more free motivation :)

Morwenn avatar Jan 26 '19 10:01 Morwenn

We probably need to check which debuggers ignore std::invoke and which ones make you step through each step (I know that the MSVC debugger at least ignores std::move, std::forward and std::function to make life easier, not sure about std::invoke). That could be additional motivation.

Morwenn avatar Feb 04 '19 15:02 Morwenn

I'll make sure to add that to the paper soon, thanks for pointing it out Morwenn!

ThePhD avatar Feb 04 '19 23:02 ThePhD

Alternative title: "Removing Barriers to Entry: std::invoke for the masses".

ThePhD avatar Feb 06 '19 00:02 ThePhD

Alternative title: "Projections for everybody: better call syntax for Callables".

ThePhD avatar Feb 06 '19 00:02 ThePhD

It's somehow the easiest and least important argument in the mix, but we could mention that it would also probably help greatly simplifying the standard wording itself in some places x)

Your wording only introduces the one required to get the feature into the standard, but imagine the heaps of INVOKE stuff that could be removed everywhere. Maybe that should come with another paper though.

Morwenn avatar Feb 07 '19 20:02 Morwenn

I'm reading the proposal again and I think that there is an impact on the standard that you didn't mention: INVOKE - and therefore std::invoke - should mirror the proposed language change to make pmo(obj, new_value); assign new_value to obj when pmo is a pointer to a member object.

Morwenn avatar Aug 27 '19 11:08 Morwenn

I'm adding this link to the motivation under a new section about compile time: https://eggs-cpp.github.io/invoke/benchmark.html

Morwenn avatar Aug 24 '20 15:08 Morwenn

Numbers on a better invoke: https://github.com/eggs-cpp/invoke

Someone started working on a compiler version of this. I'll probably do my own in GCC and Clang too...

ThePhD avatar Sep 25 '20 23:09 ThePhD