Const and non-const functors

2.3k views Asked by At

This seems like something that ought to be frequently asked and answered, but my search-fu has failed me.

I'm writing a function which I want to accept a generic callable object of some kind (including bare function, hand-rolled functor object, bind, or std::function) and then invoke it within the depths of an algorithm (ie. a lambda).

The function is currently declared like this:

template<typename T, typename F>
size_t do_something(const T& a, const F& f)
{
   T internal_value(a);
   // do some complicated things
   // loop {
   //   loop {
       f(static_cast<const T&>(internal_value), other_stuff);
       // do some more things
   //   }
   // }
   return 42;
}

I'm accepting the functor by reference because I want to guarantee that it does not get copied on entry to the function, and thus the same instance of the object is actually called. And it's a const reference because this is the only way to accept temporary objects (which are common when using hand-rolled functors or bind).

But this requires that the functor implement operator() as const. I don't want to require that; I want it to be able to accept both.

I know I can declare two copies of this method, one that accepts it as const and one as non-const, in order to cover both cases. But I don't want to do that as the comments are hiding quite a lot of code that I don't want to duplicate (including some loop constructs, so I can't extract them to a secondary method without just moving the problem).

I also know I could probably cheat and const_cast the functor to non-const before I invoke it, but this feels potentially dangerous (and in particular would invoke the wrong method if the functor intentionally implements both const and non-const call operators).

I've considered accepting the functor as a std::function/boost::function, but this feels like a heavy solution to what ought to be a simple problem. (Especially in the case where the functor is supposed to do nothing.)

Is there a "right" way to satisfy these requirements short of duplicating the algorithm?

[Note: I would prefer a solution that does not require C++11, although I am interested in C++11 answers too, as I'm using similar constructs in projects for both languages.]

2

There are 2 answers

11
Ben Voigt On BEST ANSWER

Have you tried a forwarding layer, to force inference of the qualifier? Let the compiler do the algorithm duplication, through the normal template instantiation mechanism.

template<typename T, typename F>
size_t do_something_impl(const T& a, F& f)
{
   T internal_value(a);
   const T& c_iv = interval_value;
   // do some complicated things
   // loop {
   //   loop {
       f(c_iv, other_stuff);
       // do some more things
   //   }
   // }
   return 42;
}

template<typename T, typename F>
size_t do_something(const T& a, F& f)
{
   return do_something_impl<T,F>(a, f);
}

template<typename T, typename F>
size_t do_something(const T& a, const F& f)
{
   return do_something_impl<T,const F>(a, f);
}

Demo: http://ideone.com/owj6oB

The wrapper should be completely inlined and have no runtime cost at all, except for the fact that you might end up with more template instantiations (and therefore larger code size), though that will only happen when for types with no operator()() const where both const (or temporary) and non-const functors get passed.

24
Cheers and hth. - Alf On

Answer for new relaxed requirements.

In commentary on another answer the OP has clarified/changed the requirements to…

“I'm ok with requiring that if the functor is passed in as a temporary then it must have an operator() const. I just don't want to limit it to that, such that if a functor is not passed in as a temporary (and also not a const non-temporary, of course) then it is allowed to have a non-const operator(), and this will be called”

This is then not a problem at all: just provide an overload that accepts a temporary.

There are several ways of distinguishing the original basic implementation, e.g. in C++11 an extra default template argument, and in C++03 an extra defaulted ordinary function argument.

But the most clear is IMHO to just give it a different name and then provide an overloaded wrapper:

template<typename T, typename F>
size_t do_something_impl( T const& a, F& f)
{
   T internal_value(a);
   // do some complicated things
   // loop {
   //   loop {
       f(static_cast<const T&>(internal_value), other_stuff);
       // do some more things
   //   }
   // }
   return 42;
}

template<typename T, typename F>
size_t do_something( T const& a, F const& f)
{ return do_something_impl( a, f ); }

template<typename T, typename F>
size_t do_something( T const& a, F& f)
{ return do_something_impl( a, f ); }

Note: there's no need to specify the do_something_impl instantiation explicitly, since it's inferred from the lvalue arguments.

The main feature of this approach is that it supports simpler calls, at the cost of not supporting a temporary as argument when it has non-const operator().

Original answer:

Your main goal is to avoid copying of the functor, and to accept a temporary as actual argument.

In C++11 you can just use an rvalue reference, &&

For C++03 the problem is a temporary functor instance as actual argument, where that functor has non-const operator().

One solution is to pass the burden to the client code programmer, e.g.

  • require the actual argument to be an lvalue, not a temporary, or

  • require explicit specification that the argument is a temporary, then take it as reference to const and use const_cast.

Example:

template<typename T, typename F>
size_t do_something( T const& a, F& f)
{
   T internal_value(a);
   // do some complicated things
   // loop {
   //   loop {
       f(static_cast<const T&>(internal_value), other_stuff);
       // do some more things
   //   }
   // }
   return 42;
}

enum With_temp { with_temp };

template<typename T, typename F>
size_t do_something( T const& a, With_temp, F const& f )
{
    return do_something( a, const_cast<F&>( f ) );
}

If it is desired to directly support temporaries of const type, to ease the client code programmer's life also for this rare case, then one solution is to just add an additional overload:

enum With_const_temp { with_const_temp };

template<typename T, typename F>
size_t do_something( T const& a, With_const_temp, F const& f )
{
    return do_something( a, f );
}

Thanks to Steve Jessop and Ben Voigt for discussion about this case.


An alternative and much more general C++03 way is to provide the following two little functions:

template< class Type >
Type const& ref( Type const& v ) { return v; }

template< class Type >
Type& non_const_ref( Type const& v ) { return const_cast<T&>( v ); }

Then do_something, as given above in this answer, can be called like …

do_something( a, ref( MyFunctor() ) );
do_something( a, non_const_ref( MyFunctor() ) );

Why I didn't think of that immediately, in spite of having employed this solution for other things like string building: it's easy to create complexity, harder to simplify! :)