9

I'm trying to write a function that will take a functor as an argument, invoke the functor and then return its return value wrapped in a boost::shared_ptr.

The following refuses to compile and I'm all out of ideas. I get "std::vector< std::string > does not provide a call operator" (roughly). I'm using Clang 3.1 on Mac OS X.

template< typename T >
boost::shared_ptr< T > ReturnValueAsShared(
    boost::function< T() > func )
{
  return boost::make_shared< T >( func() );
}

This is the context in which I'm trying to use it:

make_shared< packaged_task< boost::shared_ptr< std::vector< std::string > > > >(
   bind( ReturnValueAsShared< std::vector< std::string > >,
      bind( [a function that returns a std::vector< std::string >] ) ) );

EDIT: Here's a complete self-contained test case. This code fails to compile with the same error, and for the life of me I can't see what's wrong:

#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/function.hpp>
#include <boost/bind.hpp>

#include <string>
#include <vector>

std::vector< std::string > foo( std::string a )
{
  std::vector< std::string > vec;
  vec.push_back( a );
  return vec;
}

template< typename T >
boost::shared_ptr< T > ReturnValueAsShared(
    boost::function< T() > func )
{
  return boost::make_shared< T >( func() );
}

int main()
{
  auto f = boost::bind( ReturnValueAsShared< std::vector< std::string > >,
                        boost::bind( foo, std::string("a") ) );
  f();

} // main

And here's the error output:

In file included from testcase.cpp:3:
In file included from /usr/local/include/boost/function.hpp:64:
In file included from /usr/local/include/boost/preprocessor/iteration/detail/iter/forward1.hpp:47:
In file included from /usr/local/include/boost/function/detail/function_iterate.hpp:14:
In file included from /usr/local/include/boost/function/detail/maybe_include.hpp:13:
/usr/local/include/boost/function/function_template.hpp:132:18: error: type 'std::vector<std::basic_string<char>, std::allocator<std::basic_string<char> > >' does not provide a call operator
          return (*f)(BOOST_FUNCTION_ARGS);
                 ^~~~
/usr/local/include/boost/function/function_template.hpp:907:53: note: in instantiation of member function 'boost::detail::function::function_obj_invoker0<std::vector<std::basic_string<char>, std::allocator<std::basic_string<char> > >, std::vector<std::basic_string<char>, std::allocator<std::basic_string<char> > > >::invoke' requested here
        { { &manager_type::manage }, &invoker_type::invoke };
                                                    ^
/usr/local/include/boost/function/function_template.hpp:722:13: note: in instantiation of function template specialization 'boost::function0<std::vector<std::basic_string<char>, std::allocator<std::basic_string<char> > > >::assign_to<std::vector<std::basic_string<char>, std::allocator<std::basic_string<char> > > >' requested here
      this->assign_to(f);
            ^
/usr/local/include/boost/function/function_template.hpp:1042:5: note: in instantiation of function template specialization 'boost::function0<std::vector<std::basic_string<char>, std::allocator<std::basic_string<char> > > >::function0<std::vector<std::basic_string<char>, std::allocator<std::basic_string<char> > > >' requested here
    base_type(f)
    ^
/usr/local/include/boost/bind/bind.hpp:243:43: note: in instantiation of function template specialization 'boost::function<std::vector<std::basic_string<char>, std::allocator<std::basic_string<char> > > ()>::function<std::vector<std::basic_string<char>, std::allocator<std::basic_string<char> > > >' requested here
        return unwrapper<F>::unwrap(f, 0)(a[base_type::a1_]);
                                          ^
/usr/local/include/boost/bind/bind_template.hpp:20:27: note: in instantiation of function template specialization 'boost::_bi::list1<boost::_bi::bind_t<std::vector<std::basic_string<char>, std::allocator<std::basic_string<char> > >, std::vector<std::basic_string<char>, std::allocator<std::basic_string<char> > > (*)(std::basic_string<char>), boost::_bi::list1<boost::_bi::value<std::basic_string<char> > > > >::operator()<boost::shared_ptr<std::vector<std::basic_string<char>, std::allocator<std::basic_string<char> > > >, boost::shared_ptr<std::vector<std::basic_string<char>, std::allocator<std::basic_string<char> > > > (*)(boost::function<std::vector<std::basic_string<char>, std::allocator<std::basic_string<char> > > ()>), boost::_bi::list0>' requested here
        BOOST_BIND_RETURN l_(type<result_type>(), f_, a, 0);
                          ^
testcase.cpp:27:4: note: in instantiation of member function 'boost::_bi::bind_t<boost::shared_ptr<std::vector<std::basic_string<char>, std::allocator<std::basic_string<char> > > >, boost::shared_ptr<std::vector<std::basic_string<char>, std::allocator<std::basic_string<char> > > > (*)(boost::function<std::vector<std::basic_string<char>, std::allocator<std::basic_string<char> > > ()>), boost::_bi::list1<boost::_bi::bind_t<std::vector<std::basic_string<char>, std::allocator<std::basic_string<char> > >, std::vector<std::basic_string<char>, std::allocator<std::basic_string<char> > > (*)(std::basic_string<char>), boost::_bi::list1<boost::_bi::value<std::basic_string<char> > > > > >::operator()' requested here
  f();
   ^
1 error generated.

Here are some more clues. The following code compiles just fine, but that doesn't help me since this is not the code that I want :)

#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/function.hpp>
#include <boost/bind.hpp>

#include <string>
#include <vector>

std::vector< std::string > foo()
{
  std::vector< std::string > vec;
  return vec;
}

template< typename T >
boost::shared_ptr< T > ReturnValueAsShared(
    boost::function< T() > func )
{
  return boost::make_shared< T >( func() );
}

int main()
{
  auto f = boost::bind( ReturnValueAsShared< std::vector< std::string > >,
                        foo );
  f();

} // main
Lucas
  • 6,328
  • 8
  • 37
  • 49
  • 3
    Please simplify the problem and provide a minimal (non-)working example code. – Konrad Rudolph Jul 15 '12 at 19:09
  • @KerrekSB The error is `std::vector does not provide a call operator` when I try to use it as `ReturnValueAsShared< std::vector< std::string > >`. – Lucas Jul 15 '12 at 19:41
  • @KonradRudolph I'll try to minimize it. – Lucas Jul 15 '12 at 19:42
  • @KonradRudolph Test case added. – Lucas Jul 15 '12 at 20:08
  • The parameter `boost::function< T() > func` - maybe it should have `T` instead of `T()` ? – user1071136 Jul 15 '12 at 20:36
  • @user1071136 No, that fails to compile as well (as it should) since that's not the way that boost::function accepts function definitions. See [the Boost.Function docs.](http://www.boost.org/doc/libs/1_50_0/doc/html/function/tutorial.html#id1545628) – Lucas Jul 15 '12 at 20:41
  • 1
    Hm, looks like Boost just isn't that good. The `std` version of this works as soon as you add an explicit conversion to `std::function>()>` around the `bind` call. – Kerrek SB Jul 15 '12 at 20:58
  • @KerrekSB Are you saying this is a Boost bug? I was leaning towards that conclusion... – Lucas Jul 15 '12 at 21:05
  • @KerrekSB I tried wrapping the second `bind` call with `boost::function< std::vector< std::string >() >()` to cast it and it now compiles. Kerrek, make your comment an answer and I'll accept it since it led me down the right path (I just used the `boost::` version of what you said). – Lucas Jul 15 '12 at 21:09
  • Strange behaviour... http://liveworkspace.org/code/2c2b09f708a6d481e06aa39f2eda632f compiler try to convert int to boost::function. – ForEveR Jul 15 '12 at 22:17

3 Answers3

3

boost::protect is the way to go:

int main()
{
  auto f = boost::bind( ReturnValueAsShared< std::vector< std::string > >,
                        boost::protect(boost::bind( foo, std::string("a") ) ) );
  f();

} // main

This is as clean as it can get.

  • 1
    This answer could be improved by an explanation of what `boost::protect` is, what it does and maybe a link to the docs that describe it in even more detail. – Lucas Jul 16 '12 at 21:23
2

Complete rewrite, original answer was incorrect.

Error analysis

As I didn't know at first what was going wrong here, I did some analysis. I'm keeping it for future reference; see the solution below for how to avoid the problem.

bind.hpp does this:

return unwrapper<F>::unwrap(f, 0)(a[base_type::a1_]);

which in my opinion translates like this:

unwrapper<F>::unwrap(f, 0) = ReturnValueAsShared< std::vector< std::string > >
base_type::a1_ = boost::bind( foo, std::string("a") )

So what you would expect this code to do is pass the argument to the function, just the way it is. But for this to work, the expression a[base_type::a1_] would have to be of type boots:_bi::value<T>, whereas it is of the unwrapped type boost::_bi::bind_t. So instead of the functor being passed as the argument, a special overloaded version gets called:

namespace boost { namespace _bi { class list0 {
    …
    template<class R, class F, class L>
    typename result_traits<R, F>::type
    operator[] (bind_t<R, F, L> & b) const {
        return b.eval(*this);
    }
    …
} } }

This will evaluate the nullary function, instead of passing it on. So instead of an object returning a vector, the argument now is a vecotr. Subsequent steps will attempt to convert that to a boost::function and fail.

Canonical solution

Edited yet again:
It looks like this special handling of nested binds is intended as a feature. Talking on #boost with users Zao and heller, I now know that there is a protect function to counter these effects. So the canonical solution to this problem appears to be the following:

…
#include <boost/bind/protect.hpp>
…
  auto f = boost::bind( ReturnValueAsShared< std::vector< std::string > >,
    boost::protect ( boost::bind( foo, std::string("a") ) ) );
…
MvG
  • 57,380
  • 22
  • 148
  • 276
  • No, the returned value is a functor that when executed returns a `shared_ptr`. If I replace `f()` with `(*f)()`, I rightly get a compiler error that complains that `f` is not of pointer type. – Lucas Jul 15 '12 at 20:46
  • OK, You are rtight, the problem is one level deeper. It appears to me that `bind` has no overloading for `shared_ptr`, so it assumes a function object by default. But as `shared_ptr` doesn't provide a call operator, things fail. – MvG Jul 15 '12 at 20:49
  • Um, `bind` doesn't need an overload for `shared_ptr`, it's a template. Also, the error complains that `vector` doesn't have a call operator, not a `shared_ptr`. – Lucas Jul 15 '12 at 20:54
  • For your edited version: you seem to be missing that there is a `boost::bind(` in front of `ReturnValueAsShared` in `auto f = ...`. At no point does the code try to call `shared_ptr<...>::operator()`. – Lucas Jul 15 '12 at 20:58
  • OK, that about the `vector` is a good point. I'll leave my answer around for the time being, as a documentation of failed efforts, but expect to delete it eventually once a proper answer could be found. – MvG Jul 15 '12 at 21:02
  • @Lucas: Second major edit, completely rewrote the code. Got it to work. – MvG Jul 15 '12 at 22:32
  • @Lucas, I edited the answer yet again; now it includes the canonical solution as found in the boost documentation. – MvG Jul 16 '12 at 09:34
2

Some constructions (such as bind) return intermediate "expression" types which you don't actually want to capture on the nose. In that case, you mustn't capture the type via auto, and you may need to specify explicit conversions, since otherwise there isn't a unique, single user-defined conversion chain. In your case, add the explicit conversion from the bind expression to function:

typedef std::vector<std::string> G;
auto f = boost::bind(ReturnValueAsShared<G>,
             static_cast<boost::function<G()>(boost::bind(foo, std::string("a")))
                    );

(This itself doesn't actually work for me, but it does work if you use the corresponding std construc­tions.)

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • Wrapping my second `bind` call with a cast `boost::function< std::vector< std::string >() >(...)` makes everything work right. Thanks for the info. – Lucas Jul 16 '12 at 01:16