Re: [boost] [Boost-users] [Fit] formal review starts today
>> >> Quick Start:Function Objects >> >> "We can make it behave like a regular function if we
construct
the class >> as a global variable." >> >> What about a non-global >> >> sum_f sum = sum_f(); >> >> makes 'sum' not behave like a regular function other
than the
fact that >> the variable 'sum' may eventually go out of scope ? >> > > In C++, a regular function is always global, there is no such thing as local > function(sans gcc extensions).
My point is that the non-global 'sum' in my example above
behaves just
as much like a regular function as your global 'sum'. You may
want to
promote the idea of global function objects but I think that this is personally a bad idea. IMO global variables of any kind are to be avoided.
It is quite common in several modern C++ libraries to declare functions as global objects. There are many advantages to this. Why do you believe it should be avoided? Especially since it has the same effect as a free function. DO you believe free function should be avoided as well?
Global objects, whether functions or data, increases the chances for name clashes. Also accessing global data increases the chances for errors in a multi-threaded environment.
Free functions can be in a namespace, as the standard library functions are in the 'std' namespace. This helps greatly in avoiding name
conflicts.
By global variables, I mean defined at the namespace level, not solely at the global namespace level. So functions using BOOST_FIT_STATIC_FUNCTION can be defined within a namespace. Also, the function objects are `const` so there isn't an issue from multithreaded environments.
> > >> >> Quick Start:Lambdas >> >> Why do we need both BOOST_FIT_STATIC_LAMBDA and >> BOOST_FIT_STATIC_LAMBDA_FUNCTION ? I would seem that >> BOOST_FIT_STATIC_LAMBDA_FUNCTION would be adequate and >> BOOST_FIT_STATIC_LAMBDA is just redundant, mimicking lambda syntax to no >> purpose. >> > > BOOST_FIT_STATIC_LAMBDA_FUNCTION and BOOST_FIT_STATIC_FUNCTION both define a > function at global scope, and can only be used at global scope, whereas > BOOST_FIT_STATIC_LAMBDA can be used to constexpr initialize local variables > as > well.
It might be good to add that when the end-user first encounters BOOST_FIT_STATIC_LAMBDA, because the name does not suggest initialization of local objects. Furthermore I cannot imagine why one would want to use it to initialize a local object,
Really? You just said that you would prefer avoiding global functions.
I mean that for local objects I do not see the reason why I would ever use BOOST_FIT_STATIC_LAMBDA instead of initializing local objects in the normal C++ way(s). If this is not the case you need to explain what problem(s) BOOST_FIT_STATIC_LAMBDA solve in initializing local data which ordinary C++ initialization of local data could encounter. If there aren't any problems BOOST_FIT_STATIC_LAMBDA solves in initializing
local data I see no purpose for its existence.
Well, they can still be passed to constexpr functions, even if they are local, I probably shouldn't discuss BOOST_FIT_STATIC_LAMBDA in the Quick Start guide because its usage is not quite as a common.
so you might want to explain the benefit of doing so as opposed to the normal syntax for creating a lambda function.
Maybe the quick start guide isn't the place for this in the first
place.
I would hope to convince you that a good Overview of your library should come first and then all the Quick Start guides, tutorials, examples etc. would make much more sense once your end-user understands what your
library really does in terms of at least the main general functionality.
I usually start with the Quick Start guide with a library in order to get an understanding of what components are in the library and what they can do, then I start delving into the other components from there. Thats what I am showing in the Quick Start guide. I guess people have different ways of learning a library. I wonder what is needed to be explained better in a initial overview of the library.
> In fact, BOOST_FIT_LIFT uses this since it is not always clear
what
> context the user might call BOOST_FIT_LIFT. > > >> >> Quick Start:Overloading >> >> The overloading adaptors show two or more lambda functions.
Can
they >> also work with function objects ? Or a mix of lambda
functions and
>> function objects ? In fact all the reamining Quick Start
topics
show >> examples with lambda functions. Do they also work with
function
objects ? >> > > Yes it can be used with function objects. I probably should show an example > of > that as well. I used the lambdas because of the terseness of
them.
Evidently the adaptors can be used with any Callable. I think you should make that point very strongly and show that in examples also.
Agreed.
> > >> >> Quick Start:Variadic >> >> I do not understand what 'We can also make this print
function
varidiac, >> so it prints every argument passed into it.' means ? >> > > I'll try to explain that better, but basically it will print
each
argument, > so: > > print("hello", 5); // Will print "hello" and
5
I wouldn't associate that with the word 'Variadic' but I do
think you
need to explain that more clearly.
I always understood variadic to mean taking a variable number of arguments. What do you understand variadic to mean?
Your terminology is fine, but I always think of variadic in terms of variadic templates or variadic macros. After all your not making the print function variadic in the sense of taking a variable number of
arguments.
Yes, I am, so I probably need to make that more clear in the documentation.
The simple_print functionality is any of 3 different lambda > functions, with 'conditional' picking the first one that is callable and
'fix' passing in the callable as the first parameter. So I really don't understand what 'by' does here in printing each argument irregardless of
your 'Variadic' terminology. You need invoking examples and then show what the output would be for each invocation, and then it would be
easier for me and others to understand what 'by' does in this situation.
Yep, that part needs to go into more in-depth.
> > >> >> I do not think the Quick Start explains very much since it is dealing >> with adaptors of which we know almost nothing and the explanation for >> these adaptors and what they actually do is very terse. > > > Probably can expand the explanation of adaptors a little more.
Good idea. What do adaptors create ?
They create a function.
Different function objects I would imagine ? In that case how about the explanation that adaptors take Callables as input and generate function object types that adapt the original functionality of one or more Callables to some other purpose.
The examples are already show taking Callables. Are you suggesting I show an example taking a member function or something?
Global/static function, member function, std::function, boost::function, Boost Phoenix and/or Boost lambda function objects, instantiated template functions. My point is that you should show your library functionality working with as many types of Callables as possible. Instead your doc suggests that function objects and lambda functions are the only type which can be passed to your library's functionality and
you enforce this view by your examples.
I'll try to do that.
> > >> >> In the 'Signatures' section of the Overview I read: >> >> "All the functions are global function objects except
where an
explicit >> template parameter is required." I honestly don't
know what this is
>> supposed to mean. Does this refer to when function objects
are
referred >> to as parameters to the adaptors, functions, and utilities of the library >> ? >> > > I don't understand what you are asking. It means that the function is > written > like this in the documentation: > > template<class IntegralConstant> > constexpr auto if_(IntegralConstant); > > But its actually a function object like this in the code: > > struct if_f > { > template<class IntegralConstant> > constexpr auto operator()(IntegralConstant) const; > }; > const constexpr if_f if_ = {}; > > However, `if_c` is written like this in the documentation: > > template
> constexpr auto if_c(F); > > It requires the bool `B` template parameter explicity. So in the code it is > written as a function and not as a function object. I don't understand to what you are referring when you say
'function'.
Are you talking about adaptors in your library, functions in your library, or what ?
I am talking about all functions that are defined in the library, that includes adaptors as well. I am not sure how to make that clearer.
Please try to understand that your use of the word 'function' is very broad but that the word 'function'
in C++ has a much
narrower meaning.
By function, I mean something like in the example:
template<class IntegralConstant> constexpr auto if_(IntegralConstant);
I think everyone agrees that is a function in C++.
I call that a function template.
Which is a family of functions.
My definition for 'function' is not the > same as yours. I know you feel differently but please realize that the term 'function', to mean any and every type of callable in C++, is not a
universally agreed on terminology. I would bet that for the vast majority of C++ programmers a function is:
ReturnType FunctionName(ZeroOrMoreFunctionParameters);
Yes, which can happen at class scope or namespace scope, and it can also be templated.
On 03/09/2016 08:43 AM, paul Fultz wrote:
...I guess people have different ways of learning a library. I wonder what is needed to be explained better in a initial overview of the library.
Please do not take it a a criticism of any kind. That's just an impression I've got. I could be way off the mark.. I often am... I read the docs... twice... well, I tried... :-) I was not able to find an answer to a nagging question -- why I might need the library? What does it do that the standard C++ does not? To me the Quick Start felt more like Quick Start to Confusion. :-) Literally I felt like the deeper into the docs I was going the more alarmed I was. "We can make it (the functor) behave like a regular function" In all honesty I don't think I ever had such a need. Usually IMO the conversion is the other way around -- a reg. function into a functor. I think it happens pretty much automatically. "if we construct the class as a global variable" Should I be excited about it? Globals are often a royal pain. Do I want to construct it a a global variable? | "BOOST_FIT_STATIC_FUNCTION" | A macro? For C++14? Really? And given you mention it about 20 times just in the Quick Start section it seems quite pivotal for the library... Should I be excited about it? Macros are often a royal pain... Wrapping functors and lambdas in a macro?.. seems like I need quite a bit of convincing I might want that. For a library to be accepted the user has to understand the purpose/value of it and to get excited about it. I did not get it. In fact, I got the opposite... but I a not the brightest "bulb" in the pack... V. ||
On Tuesday, March 8, 2016 4:58 PM, Vladimir Batov
wrote: On 03/09/2016 08:43 AM, paul Fultz wrote: ...I guess people have different ways of learning a library. I wonder what is needed to be explained better in a initial overview of the library.
Please do not take it a a criticism of any kind. That's just an impression I've got. I could be way off the mark.. I often am... I read the docs... twice... well, I tried... :-) I was not able to find an answer to a nagging question -- why I might need the library? What does it do that the standard C++ does not? To me the Quick Start felt more like Quick Start to Confusion. :-) Literally I felt like the deeper into
the docs I was going the more alarmed I was.
Maybe I need a little more explanation of components in the Quick Start Guide. Perhaps, also, a comparison of writing some of the examples without using the library. The recursive print example is simple, but the technique could apply anytime you needed generic serialization of data. I have written code like that without using this library, so I can see benefit of using it for this particular case. So perhaps, writing a comparison without the library might make that clearer.
"We can make it (the functor) behave like a regular function"
I assume by functor, you mean function, as the library doesn't support functors and is beyond the scope of this library.
In all honesty I don't think I ever had such a need. Usually IMO the conversion is the other way around -- a reg. function into a functor. I
think it happens pretty much automatically.
Well, the library provides help for that as well, because currently in C++ you can't pass generic functions to other functions, like this: std::accumulate(v.begin(), v.end(), 0, std::max); // Compile error However, BOOST_FIT_LIFT will let you do that: std::accumulate(v.begin(), v.end(), 0, BOOST_FIT_LIFT(std::max));
"if we construct the class as a global variable"
Should I be excited about it? Globals are often a royal pain. Do I want
to construct it a a global variable? |
Whats the pain about these Global variables? They are const, can be
namespaced, and work just like free functions. However, they do have several
advantages.
First, by making them objects we can turn functions into first-class citizens.
This allows for functions to be easily passed around to other functions. In
fact, almost all the functions in this library are declared this way. This
make the functions(and adaptors) easily composable. For example, if you wanted
to write a function to do for_each over a tuple, you can easily compose the
`unpack` adaptor with the `by` adaptor:
BOOST_FIT_STATIC_FUNCTION(for_each_tuple) = compose(unpack, by);
So the `by` adaptor will call a function for each parameter, and the `unpack`
adaptor will unpack the elements of the tuple to each parameter. By composing
them together we can call a function for each element in a tuple. So, you can
write a function to print each value in a tuple, like this:
auto t = std::make_tuple(1, 2);
for_each_tuple([](auto x) { std::cout << x << std::endl; })(t);
So by passing functions to other functions, we can easily write some very
sophisticated functions without having to resort to metaprogramming. Just as a
comparison, here is how you could write for_each_tuple without this library:
namespace detail
{
template
"BOOST_FIT_STATIC_FUNCTION" | A macro? For C++14? Really? And given you mention it about 20 times just in the Quick Start section it seems quite pivotal for the library... Should I be excited about it? Macros are often a royal pain... Wrapping functors and lambdas in a macro?.. seems like I need quite a bit of
convincing I might want that.
C++17 will be adding support for inline variables, so in the future this macro will be unnecessary. For now, it will take care of statically initializing the function and avoiding possible ODR issues. Furthermore, dealing with inconsistencies and bugs across multiple platforms is a real pain. For example, MSVC has lots of bugs with constexpr that can affect statically initializing the function object. So this macro provides workarounds so the it can be initialized statically. I don't see the macro as problematic, and without the macro is more problematic. However, you can write it without the macro like this: template<class T> struct static_const_storage { static constexpr T value = T(); }; template<class T> constexpr T static_const_storage<T>::value; template<class T> constexpr const T& static_const_var(const T&) { return detail::static_const_storage<T>::value; } static constexpr auto&& for_each_tuple = static_const_var(compose(unpack, by)); This of course, will only work on a fairly compliant C++11 compilers. It doesn't work on MSVC. Also, it won't work when using lambdas. No doubt, C++14 gets rid of a lot of macro usages, but C++14 is still lacking in many areas, so macros are still needed to fill in these gaps.
For a library to be accepted the user has to understand the purpose/value of it and to get excited about it. I did not get it. In fact, I got the opposite... but I a not the brightest "bulb" in the
pack...
Thanks for the feedback, I probably should discuss more of the advantages of using function objects in the documentation to make it clear to more people.
V. ||
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
On 2016-03-09 18:34, paul Fultz wrote:
On Tuesday, March 8, 2016 4:58 PM, Vladimir Batov
wrote: Maybe I need a little more explanation of components in the Quick Start Guide. Perhaps, also, a comparison of writing some of the examples without using the library...
I think that's an excellent idea.
... a functor. I think it happens pretty much automatically.
Well, the library provides help for that as well, because currently in C++ you can't pass generic functions to other functions, like this:
std::accumulate(v.begin(), v.end(), 0, std::max); // Compile error However, BOOST_FIT_LIFT will let you do that:
std::accumulate(v.begin(), v.end(), 0, BOOST_FIT_LIFT(std::max));
Isn't it resolved with std::accumulate(v.begin(), v.end(), 0, boost::bind(std::max<int>, _1, _2)); Seems like std binders take care of the generic functions' "awkwardness". Am I missing anything?
"if we construct the class as a global variable"
Should I be excited about it? Globals are often a royal pain. Do I want
to construct it a a global variable?
Whats the pain about these Global variables? They are const, can be namespaced, and work just like free functions. However, they do have several advantages.
I much prefer creating things locally to minimize the scope and the potential for mis-use. If I (or someone) create something (an object), there is a potential desire to stick some stateful info/data into it. Then, the complexity snow-balls as it becomes a bottleneck in multi-threaded env.
First, by making them objects we can turn functions into first-class citizens.
Why can't I use the standard binders to achieve that?.. Or what do I gain by using Fit instead?
skipped considerable chunk...
What I seem to hear is that Fit helps to overcome some plain-function limitations. I think you might want to consider highlighting what Fit adds compared to the standard binders. I felt that quite a few of your examples could be done using those binders. I might be wrong though. V.
On Wednesday, March 9, 2016 at 2:13:54 AM UTC-6, work wrote:
On 2016-03-09 18:34, paul Fultz wrote:
On Tuesday, March 8, 2016 4:58 PM, Vladimir Batov
javascript:> wrote: Maybe I need a little more explanation of components in the Quick Start Guide. Perhaps, also, a comparison of writing some of the examples without using the library...
I think that's an excellent idea.
I do a comparison of something similar here: http://pfultz2.com/blog/2014/12/12/compare-overloading-2/
... a functor. I think it happens pretty much automatically.
Well, the library provides help for that as well, because currently in C++ you can't pass generic functions to other functions, like this:
std::accumulate(v.begin(), v.end(), 0, std::max); // Compile error However, BOOST_FIT_LIFT will let you do that:
std::accumulate(v.begin(), v.end(), 0, BOOST_FIT_LIFT(std::max));
Isn't it resolved with
std::accumulate(v.begin(), v.end(), 0, boost::bind(std::max<int>, _1, _2));
Seems like std binders take care of the generic functions' "awkwardness". Am I missing anything?
Well the bind should be unnecessary. You should be able to write: std::accumulate(v.begin(), v.end(), 0, &std::max<int>); I guess this is not the best example. In generic code, you may not know that the container is a container of ints, so by passing along `BOOST_FIT_LIFT(std::max)`, the types will be deduced. There are other cases where the types have to be deduced, because the type are either unknown to the callee or is called with different types. If it were an algorithm over tuples, not everything will be `int`, so its not as easy as writing `std::max<int>`.
"if we construct the class as a global variable"
Should I be excited about it? Globals are often a royal pain. Do I want
to construct it a a global variable?
Whats the pain about these Global variables? They are const, can be namespaced, and work just like free functions. However, they do have several advantages.
I much prefer creating things locally to minimize the scope and the potential for mis-use.
Well you create function globally, and these are just functions, the same misuse that applies to functions applies here.
If I (or someone) create something (an object), there is a potential desire to stick some stateful info/data into it. Then, the complexity snow-balls as it becomes a bottleneck in multi-threaded env.
These functions are initialized at compile-time and must be ConstCallable, so if data is added to the functions it must be initialized without side-effects, and cannot mutate the data when the function is called.
First, by making them objects we can turn functions into first-class citizens.
Why can't I use the standard binders to achieve that?.. Or what do I gain by using Fit instead?
Bind is a simple form of lambda expressions, so you can express a lot with lambda expressions. Futhermore, the library provides the lazy adaptor which works just like std::bind with the addition of being constexpr-friendly. However, there is a lot that you can't do with bind. You can't do overloading nor recursion nor projections with bind. Even though you can do composition and partial application, there can be complications when dealing with nested bind expressions. So sometimes, its better to have a component that can just handle composition or just handle partial application. Also, bind cannot handle variadiac parameters as well.
skipped considerable chunk...
What I seem to hear is that Fit helps to overcome some plain-function limitations. I think you might want to consider highlighting what Fit adds compared to the standard binders. I felt that quite a few of your examples could be done using those binders. I might be wrong though.
I think it would be could idea to compare the library to the capabilities of bind.
Le 09/03/2016 09:13, work a écrit :
On 2016-03-09 18:34, paul Fultz wrote:
On Tuesday, March 8, 2016 4:58 PM, Vladimir Batov
wrote: ... a functor. I think it happens pretty much automatically.
Well, the library provides help for that as well, because currently in C++ you can't pass generic functions to other functions, like this:
std::accumulate(v.begin(), v.end(), 0, std::max); // Compile error However, BOOST_FIT_LIFT will let you do that:
std::accumulate(v.begin(), v.end(), 0, BOOST_FIT_LIFT(std::max));
Isn't it resolved with
std::accumulate(v.begin(), v.end(), 0, boost::bind(std::max<int>, _1, _2));
Seems like std binders take care of the generic functions' "awkwardness". Am I missing anything? I want to type
std::accumulate(v.begin(), v.end(), 0, std::max); There is a proposal that will make this code correct. I believe that the closest to that is std::accumulate(v.begin(), v.end(), 0, OSAFA(std::max)); Where OSAFA mean Overload Set As Function Arguments [1] . The macro BOOST_FIT_LIFT tries to go in this direction. You can always continue to use bind, but I prefer to don't have to add the <int> annotation. As said before, I'm for the use of macros when we have a future language feature. We need to find a good name. A user is not forced to use the macro if this goes against its style or company coding rules/guidelines.
"if we construct the class as a global variable"
Should I be excited about it? Globals are often a royal pain. Do I want
to construct it a a global variable?
Whats the pain about these Global variables? They are const, can be namespaced, and work just like free functions. However, they do have several advantages.
I much prefer creating things locally to minimize the scope and the potential for mis-use. If I (or someone) create something (an object), there is a potential desire to stick some stateful info/data into it. Then, the complexity snow-balls as it becomes a bottleneck in multi-threaded env.
I believe that the library should NOT promote global functions or variables. The library should just provide some mechanism that help to define those global functions. IMO, most of the documentation should ignore all these STATIC macros.
First, by making them objects we can turn functions into first-class citizens.
Why can't I use the standard binders to achieve that?.. You can. Or what do I gain by using Fit instead? This is better question.
I believe that we need to see the Fit library as two different concerns: HOF * provide some high order functions that are useful when doing functional programming SHOF * provide some mechanisms that help to define global high order functions You can ignore the second part (SHOF - static high order function) if you don't want to define global high order functions or if you prefer to define a function object to this end. The first part HOF, includes a lot of useful functions. IMHO the library need yet some modifications here and there, but I find that there is an added value for those that want to use functional programming.
skipped considerable chunk...
What I seem to hear is that Fit helps to overcome some plain-function limitations. I think you might want to consider highlighting what Fit adds compared to the standard binders. I felt that quite a few of your examples could be done using those binders. I might be wrong though.
I don't think you can do all with binders, but maybe I'm wrong. Bind is a way to transform a non high order function on one that is high-order. Maybe you can show how binding can be used to define some of the function the library provides, compose, fix, flow, ...match, partial, for example. We need to have more functions and ways to compose them. Fit goes in this direction. Best, Vicente [1] http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0119r1.pdf
Le 09/03/2016 08:34, paul Fultz a écrit :
On 03/09/2016 08:43 AM, paul Fultz wrote: ...I guess people have different ways of learning a library. I wonder what is needed to be explained better in a initial overview of the library. Please do not take it a a criticism of any kind. That's just an impression I've got. I could be way off the mark.. I often am... I read
On Tuesday, March 8, 2016 4:58 PM, Vladimir Batov
wrote: the docs... twice... well, I tried... :-) I was not able to find an answer to a nagging question -- why I might need the library? What does it do that the standard C++ does not? To me the Quick Start felt more like Quick Start to Confusion. :-) Literally I felt like the deeper into the docs I was going the more alarmed I was. Maybe I need a little more explanation of components in the Quick Start Guide. Perhaps, also, a comparison of writing some of the examples without using the library. The recursive print example is simple, but the technique could apply anytime you needed generic serialization of data. I have written code like that without using this library, so I can see benefit of using it for this particular case. So perhaps, writing a comparison without the library might make that clearer.
Yes, please, show us some examples.
"We can make it (the functor) behave like a regular function" I assume by functor, you mean function, as the library doesn't support functors and is beyond the scope of this library. Some parts of the C++ community name a function object a functor. It ha nothing to be with the applicative, functor, monad in type classes.
In all honesty I don't think I ever had such a need. Usually IMO the conversion is the other way around -- a reg. function into a functor. I think it happens pretty much automatically. Well, the library provides help for that as well, because currently in C++ you can't pass generic functions to other functions, like this:
std::accumulate(v.begin(), v.end(), 0, std::max); // Compile error
However, BOOST_FIT_LIFT will let you do that:
std::accumulate(v.begin(), v.end(), 0, BOOST_FIT_LIFT(std::max)); Maybe it is worth noting that these is a C++17/20 proposal to manage with this case.
[1] p0119r1 - Overload sets as function arguments http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0119r1.pdf
"if we construct the class as a global variable"
Should I be excited about it? Globals are often a royal pain. Do I want to construct it a a global variable? | Whats the pain about these Global variables? They are const, can be namespaced, and work just like free functions. However, they do have several advantages.
First, by making them objects we can turn functions into first-class citizens.
Note that we do that now with function objects and lambdas. This is a major argument that belongs to the motivation. You library works with and build high order functions (HOF).
This allows for functions to be easily passed around to other functions. In fact, almost all the functions in this library are declared this way. This make the functions(and adaptors) easily composable. For example, if you wanted to write a function to do for_each over a tuple, you can easily compose the `unpack` adaptor with the `by` adaptor:
BOOST_FIT_STATIC_FUNCTION(for_each_tuple) = compose(unpack, by); As others have noted you should separate the the high order function definition from the possibility to define them globally.
auto for_each_tuple = compose(unpack, by); An alternative to use global function is to define function factories auto for_each_tuple() { return compose(unpack, by); } However this needs an extra level of parenthesis for_each_tuple()([](auto x) { std::cout << x << std::endl; })(t); Maybe it is worth talking of what is behind this style (point-free style or tacit programming) https://en.wikipedia.org/wiki/Tacit_programming
So the `by` adaptor will call a function for each parameter, and the `unpack` adaptor will unpack the elements of the tuple to each parameter. By composing them together we can call a function for each element in a tuple. So, you can write a function to print each value in a tuple, like this:
auto t = std::make_tuple(1, 2); for_each_tuple([](auto x) { std::cout << x << std::endl; })(t);
So by passing functions to other functions, we can easily write some very sophisticated functions without having to resort to metaprogramming. Just as a comparison, here is how you could write for_each_tuple without this library:
namespace detail { template
void for_each(T&& t, F f, seq ) { auto l = { (f(std::get<Is>(t)), 0)... }; } } template
void for_each_tuple(std::tuple const& t, F f) { detail::for_each(t, f, detail::gen_seq ()); } It is more code, with more C++ cleverness going on.
This is s a good example, and of course will need to compare performances at compile time and run-time.
Secondly, by making them objects, it allows us to decorate or enhance functions. C++ doesn't have support for python-like decorators, so if you want to enhance a function, it will need to be a function object through and through.
"BOOST_FIT_STATIC_FUNCTION" | A macro? For C++14? Really? And given you mention it about 20 times just in the Quick Start section it seems quite pivotal for the library... Should I be excited about it? Macros are often a royal pain... Wrapping functors and lambdas in a macro?.. seems like I need quite a bit of
convincing I might want that.
C++17 will be adding support for inline variables, so in the future this macro will be unnecessary. For now, it will take care of statically initializing the function and avoiding possible ODR issues. Any reference to something that will appear in the future standard and describe that your macros are a way to emulate a future feature would be much appreciated. Could you elaborate how inline variables are related?
Furthermore, dealing with inconsistencies and bugs across multiple platforms is a real pain. For example, MSVC has lots of bugs with constexpr that can affect statically initializing the function object. Not everyone needs constexpr. So maybe this use case should be moved to
So this macro provides workarounds so the it can be initialized statically. I don't see the macro as problematic, and without the macro is more problematic. Only if the user needs to declare them globally. However, you can write it without the macro like this:
template<class T> struct static_const_storage { static constexpr T value = T(); };
template<class T> constexpr T static_const_storage<T>::value;
template<class T> constexpr const T& static_const_var(const T&) { return detail::static_const_storage<T>::value; } I believe that there are a lot of users that would prefer to write the code that follows once a library provide the previous one static constexpr auto&& for_each_tuple = static_const_var(compose(unpack, by));
This of course, will only work on a fairly compliant C++11 compilers. It doesn't work on MSVC. Does it works with a MSVC C++14? Also, it won't work when using lambdas. No doubt, C++14 gets rid of a lot of macro usages, but C++14 is still lacking in many areas, so macros are still needed to fill in these gaps. You need to let the user choose. If the user is using a C++14 compliant compiler, would it need the macros? If not it is not worth presenting it as something capital. This belong to the workaround and emulations and I could appreciate having those macros, but having the equivalent C++ code is better.
For a library to be accepted the user has to understand the purpose/value of it and to get excited about it. I did not get it. In fact, I got the opposite... but I a not the brightest "bulb" in the pack...
Thanks for the feedback, I probably should discuss more of the advantages of using function objects in the documentation to make it clear to more people. Yes I've said not all the people knows about High order functional
I agree with that and I suggested Paul from the beginning to move this to an Advanced section. The fact that the library uses it to define the global function is of no concern for the users. Only if a user wants to define a global HOF, then those macros can help them. the advanced usage. AN alternative that you could or not have is to require better compilers. as e.g. Boost.Hana does. programming. You should explain and make reference to external resources as needed. Best, Vicente
On Wednesday, March 9, 2016 at 2:33:55 AM UTC-6, Vicente J. Botet Escriba wrote:
Le 09/03/2016 08:34, paul Fultz a écrit :
On Tuesday, March 8, 2016 4:58 PM, Vladimir Batov <
On 03/09/2016 08:43 AM, paul Fultz wrote: ...I guess people have different ways of learning a library. I wonder what is needed to be explained better in a initial overview of the library. Please do not take it a a criticism of any kind. That's just an impression I've got. I could be way off the mark.. I often am... I read the docs... twice... well, I tried... :-) I was not able to find an answer to a nagging question -- why I might need the library? What does it do that the standard C++ does not? To me the Quick Start felt more like Quick Start to Confusion. :-) Literally I felt like the deeper into the docs I was going the more alarmed I was. Maybe I need a little more explanation of components in the Quick Start Guide. Perhaps, also, a comparison of writing some of the examples without using the library. The recursive print example is simple, but the technique could apply anytime you needed generic serialization of data. I have written code
that without using this library, so I can see benefit of using it for
Vladimi...@constrainttec.com javascript:> wrote: like this
particular case. So perhaps, writing a comparison without the library might make that clearer. Yes, please, show us some examples.
I do show some comparisons here: http://pfultz2.com/blog/2014/12/06/compare-overloading-1/ http://pfultz2.com/blog/2014/12/12/compare-overloading-2/ I should try to integrate them into the documentation.
"We can make it (the functor) behave like a regular function" I assume by functor, you mean function, as the library doesn't support functors and is beyond the scope of this library. Some parts of the C++ community name a function object a functor. It ha nothing to be with the applicative, functor, monad in type classes.
In all honesty I don't think I ever had such a need. Usually IMO the conversion is the other way around -- a reg. function into a functor. I think it happens pretty much automatically. Well, the library provides help for that as well, because currently in C++ you can't pass generic functions to other functions, like this:
std::accumulate(v.begin(), v.end(), 0, std::max); // Compile error
However, BOOST_FIT_LIFT will let you do that:
std::accumulate(v.begin(), v.end(), 0, BOOST_FIT_LIFT(std::max)); Maybe it is worth noting that these is a C++17/20 proposal to manage with this case.
[1] p0119r1 - Overload sets as function arguments http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0119r1.pdf
Thanks for the link, I will add a reference to that.
"if we construct the class as a global variable"
Should I be excited about it? Globals are often a royal pain. Do I want to construct it a a global variable? | Whats the pain about these Global variables? They are const, can be namespaced, and work just like free functions. However, they do have
advantages.
First, by making them objects we can turn functions into first-class citizens. Note that we do that now with function objects and lambdas. This is a major argument that belongs to the motivation. You library works with and build high order functions (HOF). This allows for functions to be easily passed around to other functions. In fact, almost all the functions in this library are declared this way. This make the functions(and adaptors) easily composable. For example, if you wanted to write a function to do for_each over a tuple, you can easily compose
several the
`unpack` adaptor with the `by` adaptor:
BOOST_FIT_STATIC_FUNCTION(for_each_tuple) = compose(unpack, by); As others have noted you should separate the the high order function definition from the possibility to define them globally.
auto for_each_tuple = compose(unpack, by);
An alternative to use global function is to define function factories
auto for_each_tuple() { return compose(unpack, by); }
Alternatively, you could be written like this:
template
However this needs an extra level of parenthesis
for_each_tuple()([](auto x) { std::cout << x << std::endl; })(t);
Maybe it is worth talking of what is behind this style (point-free style or tacit programming)
Yes, thats a good idea.
So the `by` adaptor will call a function for each parameter, and the
adaptor will unpack the elements of the tuple to each parameter. By composing them together we can call a function for each element in a tuple. So, you can write a function to print each value in a tuple, like this:
auto t = std::make_tuple(1, 2); for_each_tuple([](auto x) { std::cout << x << std::endl; })(t);
So by passing functions to other functions, we can easily write some very sophisticated functions without having to resort to metaprogramming. Just as a comparison, here is how you could write for_each_tuple without this
`unpack` library:
namespace detail { template
void for_each(T&& t, F f, seq ) { auto l = { (f(std::get<Is>(t)), 0)... }; } } template
void for_each_tuple(std::tuple const& t, F f) { detail::for_each(t, f, detail::gen_seq ()); } It is more code, with more C++ cleverness going on.
This is s a good example, and of course will need to compare performances at compile time and run-time.
Secondly, by making them objects, it allows us to decorate or enhance functions. C++ doesn't have support for python-like decorators, so if
to enhance a function, it will need to be a function object through and through.
"BOOST_FIT_STATIC_FUNCTION" | A macro? For C++14? Really? And given you mention it about 20 times just in the Quick Start section it seems quite pivotal for the library... Should I be excited about it? Macros are often a royal pain... Wrapping functors and lambdas in a macro?.. seems like I need quite a bit of I agree with that and I suggested Paul from the beginning to move this to an Advanced section. The fact that the library uses it to define the global function is of no concern for the users. Only if a user wants to define a global HOF, then those macros can help
you want them.
Its not just needed for HOF, you need it to define any global function. I could show alternative way of defining them without the macro, using a free function. However, I would prefer to move the non-macro version to the advance section, since it a little more complicated, whereas the macro is much simpler.
convincing I might want that.
C++17 will be adding support for inline variables, so in the future this macro will be unnecessary. For now, it will take care of statically initializing the function and avoiding possible ODR issues. Any reference to something that will appear in the future standard and describe that your macros are a way to emulate a future feature would be much appreciated. Could you elaborate how inline variables are related?
I'll try to find the papers that was written on them.
Furthermore, dealing with inconsistencies and bugs across multiple
is a real pain. For example, MSVC has lots of bugs with constexpr that can affect statically initializing the function object. Not everyone needs constexpr. So maybe this use case should be moved to
platforms the advanced usage.
However, the user needs constexpr, even if the function is not constexpr, if they want to initialize the variable statically.
AN alternative that you could or not have is to require better compilers. as e.g. Boost.Hana does.
However, I would like portability.
So this macro provides workarounds so the it can be initialized statically. I don't see the macro as problematic, and without the macro is more problematic. Only if the user needs to declare them globally.
Yes.
However, you can write it without the macro like this:
template<class T> struct static_const_storage { static constexpr T value = T(); };
template<class T> constexpr T static_const_storage<T>::value;
template<class T> constexpr const T& static_const_var(const T&) { return detail::static_const_storage<T>::value; } I believe that there are a lot of users that would prefer to write the code that follows once a library provide the previous one static constexpr auto&& for_each_tuple = static_const_var(compose(unpack, by));
This of course, will only work on a fairly compliant C++11 compilers. It doesn't work on MSVC. Does it works with a MSVC C++14?
It doesn't, or at least it is problematic. First, the variable does not have a unique address across translation units. Secondly, there is problems when this is combined with pre-compiled headers. And thirdly, as mentioned before, the constexpr bugs can make this not work at all. So the macro takes care of all these issues for the user, and I would rather not promote writing something that is not portable.
Also, it won't work when using lambdas. No doubt, C++14 gets rid of a lot of macro usages, but C++14 is still lacking in many areas, so macros are still needed to fill in these gaps. You need to let the user choose. If the user is using a C++14 compliant compiler, would it need the macros?
For lambdas yes. In C++17, all these macros will be unnecessary.
If not it is not worth presenting it as something capital. This belong to the workaround and emulations and I could appreciate having those macros, but having the equivalent C++ code is better.
The macros are there so the user doesn't have to think about whether they need a workaround or emulation. I could add an advance section that explains how to some these things without a macro with a note about portability. The thing is I want something simple the user can start with when using the library. Explaining a construct that has caveats should go in the advance section. The macro does not have caveats.
For a library to be accepted the user has to understand the purpose/value of it and to get excited about it. I did not get it. In fact, I got the opposite... but I a not the brightest "bulb" in the pack...
Thanks for the feedback, I probably should discuss more of the
using function objects in the documentation to make it clear to more
advantages of people. Yes I've said not all the people knows about High order functional programming. You should explain and make reference to external resources as needed.
Yes, good point.
Best, Vicente
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
participants (5)
-
paul Fultz
-
Paul Fultz II
-
Vicente J. Botet Escriba
-
Vladimir Batov
-
work