Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Const and non-const functors

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

like image 778
Miral Avatar asked Dec 12 '13 02:12

Miral


People also ask

How is it possible to have both const and non-const version of a function?

There are legitimate uses of having two member functions with the same name with one const and the other not, such as the begin and end iterator functions, which return non-const iterators on non-const objects, and const iterators on const objects, but if it's casting from const to do something, it smells like fish.

Can a const function call a non-const function?

const member functions may be invoked for const and non-const objects. non-const member functions can only be invoked for non-const objects. If a non-const member function is invoked on a const object, it is a compiler error.

How do I remove a const?

The statement int* c = const_cast<int>(b) returns a pointer c that refers to a without the const qualification of a . This process of using const_cast to remove the const qualification of an object is called casting away constness. Consequently the compiler does allow the function call f(c) .


1 Answers

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.

like image 66
Ben Voigt Avatar answered Sep 21 '22 09:09

Ben Voigt