Boost logo

Boost :

Subject: Re: [boost] [Fit] Review
From: Paul Fultz II (pfultz2_at_[hidden])
Date: 2016-03-11 02:29:29


On Friday, March 11, 2016 at 12:19:15 AM UTC-6, Edward Diener wrote:
>
> On 3/11/2016 12:10 AM, Paul Fultz II wrote:
> >
> >
> >>>
> >>> We can define functions by simply composing other functions together
> and
> >> we
> >>> don't need to write awkward syntax nor template boilerplate for it.
> For
> >>> example, we can simply write `make_tuple` like this:
> >>>
> >>> BOOST_FIT_STATIC_FUNCTION(make_tuple) = by(decay,
> >>> construct<std::tuple>());
> >>
> >> What is the type of by(decay,construct<std::tuple>()) ?
> >>
> >
> > It is something like `by_adaptor<decltype(decay),
> construct<std::tuple>>`.
> >
> >
> >> I thought I would be able to write locally:
> >>
> >> auto make_tuple(by(decay,construct<std::tuple>()));
> >>
> >> but evidently you are saying that does not work.
> >>
> >
> > That is not what I am saying at all. It can work, the functions can be
> used
> > locally, like in the first example. However, for `make_tuple` this is
> not
> > useful at all. If `std::make_tuple` was assigned to a local variable
> then no
> > one could ever call the function.
>
> I don't understand this. I have a 'make_tuple' callable above and if I
> want to use it somewhere else I pass it to whatever functionality needs
> it. You, instead want things to be global so that they can be used by
> other functionality without passing anything around. Isn't this just an
> argument about programming style ?

Its not about programming style. Its about showing what the library is fully
capable of doing. For example, say you are library writer and have just
written a tuple class:

template<class... Ts>
struct tuple
{
    ...
};

Now you would like to provide a `make_tuple` to make it easier for users to
construct the tuple. Traditionally, you would write a function like this:

template<class... Ts>
constexpr auto make_tuple(Ts&&... xs)
    -> decltype(tuple<std::decay_t<Ts>...>(std::forward<Ts>(xs)...))
{
    return tuple<std::decay_t<Ts>...>(std::forward<Ts>(xs)...);
}

However, there is a lot of boilerplate involved with this. Plus there a lot
important things that need to happen with this function, such as using
perfect
forwardind, the `std::decay_t` type trait, and trailing decltype so it is
SFINAE-friendly. So its not completely straightforward. So instead, the Fit
library simplifies all of this, and you can just write this to provide the
function to the user:

BOOST_FIT_STATIC_FUNCTION(make_tuple) = by(decay, construct<tuple>());

Perhaps, you prefer to write the more complicated version, and that's fine,
but I feel it does a disservice to the library, not showing how it can
simplify a lot of code.

 

> I don't mind if you like your
> programming style but I doubt I am the first or last programmer who
> prefers variables created and used when needed.

Have you ever used `fopen`? That is an object that gets defined at compile-
time, not when you use it. That is when you write:

void foo()
{
    std::cout << "foo";
}

You have just created a global function object, it occupies memory, you can
take its address:

auto* address_of_foo = &foo;

Futhermore, this object wasn't initialized when you used it, it was
initialized at compile-time. The `BOOST_FIT_STATIC_FUNCTION` works exactly
the
same way:

struct foo_fn
{
    void operator()() const
    {
        std::cout << "foo";
    }
};

BOOST_FIT_STATIC_FUNCTION(foo) = foo_fn();

So, `foo` gets initialized at compile-time, and you can take its address:

auto* address_of_foo = &foo;

So I dont see how you could say that `void foo()` is acceptable and
`BOOST_FIT_STATIC_FUNCTION(foo)` since they work in the same way.
 

> In fact I have a very
> strong suspicion that "my" programming style is that of the majority of
> C++ programmers and that only a minority of programmers like global
> variables even if they can be global within a namespace scope.

Perhaps, I should use different terminolgy to describe
`BOOST_FIT_STATIC_FUNCTION`. Maybe it might be better to say that it
creates a
function(which it does) rather than say it create a global variable, since
some developers may associate global variable with other things that are not
related to `BOOST_FIT_STATIC_FUNCTION`.

 

> So what I
> am essentially saying is that while I understand you like your style and
> your macros for creating "global" callables, inferring that this is
> central to using your library, when it is not AFAICS, is going to lose
> you end-users as well as misrepresent your library. I see much more
> plusses to your library if the result of using your adapters,
> decorators, functions, and utilities can be instantiated anywhere and
> used in ways that any variable in C++ can be used. if however there are
> real technical limitations, as opposed to "I like this style because I
> it is easier for me to use with Fit", of using Fit functionality to
> create "local" objects I think you need to spell out what those
> limitations are.
>

Its not a technical limitation, but I want to make sure to show the full
depth
of the capability of what the library can do. If you do not want to use the
full capability, then thats ok, but there is no there is nothing problematic
or dangerous defining functions using `BOOST_FIT_STATIC_FUNCTION`.
 

>
> > So you use global function objects when
> > you
> > want to make the function available for consumption by others. And the
> > advantage of defining `make_tuple` this way over writing it as it is
> written
> > currently in the standard library, is that we can avoid all the template
> > boilerplate involved with this.
> >
> >
> >>>
> >>> Alternatively, you could write a factory function for this instead:
> >>>
> >>> constexpr auto make_tuple()
> >>> {
> >>> return by(decay, construct<std::tuple>());
> >>> }
> >>>
> >>> Of course this requires the user to write `make_tuple()(xs...)`, which
> I
> >>> don't
> >>> think any user would expect this, especially since `std::make_tuple`
> >> doesn't
> >>> work like that. So instead, it can be wrapped in a template function:
> >>>
> >>> template<class... Ts>
> >>> constexpr auto make_tuple(Ts&&... xs)
> >>> -> declype(by(decay,
> >>> construct<std::tuple>())(std::forward<Ts>(xs)...))
> >>> {
> >>> return by(decay,
> >> construct<std::tuple>())(std::forward<Ts>(xs)...);
> >>> }
> >>>
> >>> Which is a lot of boilerplate to compose some simple functions.
> >>>
> >>>
> >>>> If
> >>>> there really is some other reason it is completely lost to me. If you
> >>>> would like to point to me in your doc where you explain the use of
> >>>> "global variables" as being a key feature of your library or as being
> >>>> necessary to use the functionality of your library, I would be glad
> to
> >>>> read about it and ask further about it here.
> >>>>
> >>>
> >>> It is not a necessary feature, but it is an important feature
> >> nonetheless,
> >>> and
> >>> I need to spend more time explaining its usefulness.
> >>>
> >>>
> >>>> If, OTOH, it is just your
> >>>> preference to use global objects as opposed to the various forms of
> >>>> local instantiation, I really wish you would just say that rather
> than
> >>>> acting like your library does not work correctly somehow other than
> with
> >>>> global variables.
> >>>>
> >>>
> >>> The composability of the adaptors and functions applies to both global
> >> and
> >>> local functions. The adaptors provide a simple way to create functions
> >> to be
> >>> passed locally, such as creating a comparator for std::sort(ie
> >>> `std::sort(first, last, by(&employee::name, _<_))`). However, they can
> >> also
> >>> be
> >>> used to define global/free functions in a much simpler way as
> well(such
> >> as
> >>> writing `std::make_tuple`), which I think is equally important.
> >>>
> >>> Furthermore, in a quick start guide or introduction, I want to be able
> to
> >>> demonstrate the capabilities of the library in a way to show its
> >> usefulness,
> >>> and why someone would choose to use this library. Perhaps, you are ok
> >> with
> >>> awkward syntax or template boilerplate in writing your functions, and
> >> would
> >>> prefer to only use the adaptors for local functions. However, there
> are
> >>> others
> >>> who will find writing global function objects with the adaptors very
> >> useful,
> >>> so I want to be able to show those capabilities in the introduction of
> >> the
> >>> library. I do, however, need to discuss some of the misperceived
> issues
> >> with
> >>> using global function objects in the documentation. As the issues
> raised
> >> in
> >>> the review were:
> >>>
> >>> 1) Namespacing - This is not a problem because all global function
> >> objects
> >>> can
> >>> be placed in a namespace.
> >>>
> >>> 2) Global state - This is not a problem either, because
> >>> `BOOST_FIT_STATIC_FUNCTION` declares the function object const with
> >>> constexpr.
> >>> So there can be no mutable or changing state internally in the object,
> >>> and it must be constructed without side effects.
> >>>
> >>> I think addressing these concerns in the documentation will help put
> >> users
> >>> at
> >>> ease when using global function objects, so they have no problem
> taking
> >>> advantage of the adaptors to build function objects globally.
> >>>
> >>>
> >>>> Maybe I have missed something but I have the intuition
> >>>> that many others have missed it likewise, from the responses of
> others
> >>>> about this issue.
> >>>>
> >>>
> >>> I agree with your intuition, and I believe the documentation should
> spend
> >>> more
> >>> time discussing these issues. I hope my email has made it somewhat
> >> clearer
> >>> to
> >>> you.
>
>
>
> _______________________________________________
> Unsubscribe & other changes:
> http://lists.boost.org/mailman/listinfo.cgi/boost
>


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk