Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"<class name> does not provide a call operator" error when trying to wrap function return value

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
like image 587
Lucas Avatar asked Jul 15 '12 19:07

Lucas


3 Answers

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.

like image 174
Thomas Heller Avatar answered Oct 18 '22 04:10

Thomas Heller


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.)

like image 25
Kerrek SB Avatar answered Oct 18 '22 05:10

Kerrek SB


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") ) ) );
…
like image 43
MvG Avatar answered Oct 18 '22 05:10

MvG