3

I am giving an example below. The program compiles and runs fine, but I am wondering whether it is theoretically undefined behaviour according to the C++11 standard; can I return the result of binding a (temporary) local function object?

Example code (edited for short):

#include <iostream>
#include <functional>

struct MyFunctionObject
{
    inline void operator() ( const std::string& name )
        { std::cout<< "Hello " << name << "!" << std::endl; }
};

std::function< void () > my_greeter( const std::string& name )
{
    MyFunctionObject fun;
    return std::bind( fun, name );
}

int main()
{
    auto g = my_greeter("sheljohn");
    g();
}

EDIT Original example:

#include <random>
#include <functional>
#include <algorithm>
#include <utility>
#include <iostream>



        /********************     **********     ********************/
        /********************     **********     ********************/



namespace foo {


/**
 * Type of the generator returned by the method 'bind' below.
 */
template <class _dist>
using generator_type = std::function< typename _dist::result_type () >;

// ------------------------------------------------------------------------

/**
 * Wrapper for C++11 random library.
 */
template <
    class _engine = std::mt19937_64,
    class _device = std::random_device
>
struct random
{
    typedef _engine engine_type;
    typedef _device device_type;
    typedef random<_engine,_device> self;

    template <class _dist>
    static inline generator_type<_dist>
    bind( _dist& distribution )
        { return std::bind( distribution, self::engine ); }

    template <class _dist>
    static inline generator_type<_dist>
    bind( _dist&& distribution )
        { return std::bind( distribution, self::engine ); }

    static engine_type engine;
    static device_type device;
};

// Initialize static engine
template <class _engine,class _device>
_device random<_engine,_device>::device;

template <class _engine,class _device>
_engine random<_engine,_device>::engine = _engine( device() );

// ------------------------------------------------------------------------

/**
 * Generic binder to standard random distributions.
 *
 * SO QUESTION: does this cause undefined behaviour?
 */

template <class _dist, class... Args>
constexpr generator_type<_dist> random_generator( Args&&... args )
    { return random<>::bind( _dist( std::forward<Args>(args)... ) ); }


}; // namespace: foo



        /********************     **********     ********************/
        /********************     **********     ********************/



int main()
{
    auto ngen = foo::random_generator< std::normal_distribution<double> >( 0.0, 1.0 );

    for(unsigned i=0; i<10; ++i) 
        std::cout<< ngen() << " " << std::endl;
}
Jonathan H
  • 7,591
  • 5
  • 47
  • 80

2 Answers2

2

The standard actually has a specific bit about calling bind() with a random number generator, in §26.5:

[ Note: These entities are specified in such a way as to permit the binding of any uniform random number generator object e as the argument to any random number distribution object d, thus producing a zero-argument function object such as given by bind(d,e). —end note ]

So what you're doing is explicitly allowed for.

Also from [func.bind.bind], the description states that when you call bind(F&& f, BoundArgs&&... bound_args) that (emphasis mine):

  • FD is the type decay_t<F>,
  • fd is an lvalue of type FD constructed from std::forward<F>(f)
  • Ti is the ith type in the template parameter pack BoundArgs
  • TiD is the type decay_t<Ti>
  • ti is the ith type in the function parameter pack bound_args
  • tid is an lvalue of type TiD constructed from std::forward<Ti>(ti)
  • ..

Returns: A forwarding call wrapper g... The effect of g(,u1, u2, ..., uM) shall be INVOKE(fd, std::forward<V1>(v1), std::forward<V2>(V2), ... std::forward<VN>(vN), result_of_t<FD cv & (V1, V2, ..., VN)>)

The section is a little confusing to me, but basically the functor and the arguments you pass in is forwarded (copied/move) into local version, fd and tid.... bind won't keep a reference to the functor (FD is not a reference type!), and the same is true of all of the arguments (the TiD types for the BoundArgs are not reference types either). So in your new example, even if fun and the string that name is a reference to both go out of scope, the bind() result is still a valid callable.

There is no UB here.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Well now my comment with random makes no sense, but I'll leave it here anyway. – Barry Jan 15 '15 at 12:17
  • Sorry I reposted the original code, that was my question, I just left the short example for tl;dr as suggested by Columbo. Many thanks for this clear answer, that's what I was after :) – Jonathan H Jan 15 '15 at 12:22
  • Well, actually what you wrote here has nothing to do with the predicted undefined behavior - it's obvious that bind and random number facilities work this way. The real problem here would be the form of bind() function. Actually you haven't cited the correct overload of the 'bind' function that is being called here - it's a completely different thing. – Ethouris Jan 16 '15 at 14:29
0

You don't have an UB, but you really have more luck than reason.

There's no undefined behavior here, but only because this here:

return std::bind( distribution, self::engine );

you pass 'distribution' by value, which means that passed is a copy of it (despite distribution being a mutable reference). As a function cannot be overloaded for both value and reference of the same type at specified position, there must exist various tricks in functions where you'd like to do that - and here it is so: bind() requires std::reference_wrapper-wrapped type to pass a reference. So you'd definitely have an undefined behavior if you did:

return std::bind( ref(distribution), self::engine );

which is available for you here. And in this case you'd pass a real reference to bind, which in this case would be a reference to a local variable passed to outside a function that embraces it.

Ethouris
  • 1,791
  • 13
  • 18
  • If I define `bind`s argument as an r-value reference, and move the declaration of the distribution into the call to `bind` in `random_generator` the program works as well. I don't think what you are saying is true; the real reason (as pointed out by @T.C. in comment) is that `std::bind` copies the function object, and that I am returning a function object by value. – Jonathan H Jan 15 '15 at 12:11
  • What do you mean "the program works as well"? You asked whether you had and undefined behavior - and "program still works" is still one of possibilities for undefined behavior. The real reason why your program still works is that you still have more luck than reason - you don't refer to the local object's internal representation. That's why you don't have any crash or something like that. It doesn't change the fact, that you'd have undefined behavior, if you used ref(distribution). – Ethouris Jan 16 '15 at 14:25
  • Barry: of course! That's exactly what I'm talking about. – Ethouris Jan 20 '15 at 18:50