9

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

Miral
  • 12,637
  • 4
  • 53
  • 93
  • 1
    Well, taking a callable object by value is pretty common. Anyway, why not put the main code in another function and have the const/non-const versions call that and pass the result of `f(...)`? – chris Dec 12 '13 at 02:04
  • In `C++11`, you would use a "Universal Reference" (`F&&`) see Scott Meyers' great info on the subject. – Chad Dec 12 '13 at 02:10
  • I have a rule of thumb: Never use a cast when an implicit conversion will work. (with regard to your `internal_value`) – Ben Voigt Dec 12 '13 at 02:21
  • @chris: If I receive it by value, it will get copied. That's not acceptable. – Miral Dec 12 '13 at 04:02
  • @BenVoigt: The cast is intended to "encourage" the functor to accept the parameter the correct way, or to generate a compile error. In particular I want to force the parameter type I want even if the functor is also templated. – Miral Dec 12 '13 at 04:03
  • @Miral: I'm not opposed to coercing the type, I just use the weakest cast that will do so. And in this case, that is no cast at all (see example coercion in my answer). Alternatively, it's nice to have `template T implicit_cast(const S& s) { return s; }` and have a new template-style cast operator which really is just a template. Or even a specialized `template const T& add_const(T& t) { return t; }` – Ben Voigt Dec 12 '13 at 04:06
  • @BenVoigt: If I don't include a cast, that will allow the called functor to accept the value as non-const. I don't want to allow that. I suppose your suggested add_const would fix that too, but that's just one more thing to write that seemed unnecessary given `static_cast` would do the trick (and static casts are already a fairly weak cast, and provably harmless in this context). I guess I could have also done `const T& r = internal_object; f(r, other);`, but I don't really see that this adds anything important. – Miral Dec 12 '13 at 04:18
  • @Miral: The problem with casts is that they disable certain types of compile-time type checking. For a local it isn't as likely, but if the source type ever changed, I think your `static_cast` could end up binding a reference to a temporary. Well, so could the initialization. I guess the `add_const` template, with type explicitly named, i.e. `add_const(internal_value)`, is safest in that regard. – Ben Voigt Dec 12 '13 at 04:29
  • Looking at the original code again I remember why the `static_cast` was there and not a separate variable: I oversimplified, and the value being passed to the functor is itself a temporary value returned from a subfunction. I could assign that to a reference local, but that's a non-standard extension and at least looks dodgy; I could assign it to a non-reference local but that implies more permanence than I wanted. Hence the cast. Granted again something like `add_const` would have worked but I don't think there's anything particularly bad about the `static_cast`. – Miral Dec 12 '13 at 06:53

2 Answers2

3

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.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • how does this work for a temporary functor argument whose `operator()` is non-`const`? – Cheers and hth. - Alf Dec 12 '13 at 02:40
  • as I see that's a *non-solution*. – Cheers and hth. - Alf Dec 12 '13 at 02:45
  • @Alf: Why? The question explicitly asks for a way to both have const references call the `operator()() const` and non-const references call `operator()() /* not const */`. And explicitly says that calling the latter on a const reference is wrong. – Ben Voigt Dec 12 '13 at 02:46
  • @Ben: I don't think the questioner is *happy* about the fact that to allow an rvalue argument they end using a const reference. I think they want to be able to call non-const `operator()` on non-const rvalue arguments, and are struggling with the fact that C++03 won't let them. So although the questioner says calling non-const `operator()` on a const reference is wrong, I don't think they mean to imply that calling `operator()` on a non-const rvalue argument would be wrong, if only they could figure out a way to achieve that. Which ofc is what rvalue references do in C++11. – Steve Jessop Dec 12 '13 at 03:00
  • @Steve: It's certainly possible the asker feels that way, but that's not what he *says*. What he says is that two overloads would have the desired behavior, but suffer the drawback of code duplication. Which the shim layer solves. – Ben Voigt Dec 12 '13 at 03:04
  • @BenVoigt: true, but they also say they don't want the caller to have to specify `operator() const`. They say they can do two overloads, but it is not wholly clear to me that the two overloads really would have the desired behavior, once the questioner implemented them, tried to pass in an rvalue argument with a non-const `operator()`, and got an error. I don't think we have enough evidence that they've thought it through to that point, which is why I described that case in my answer when discussing what amounts to your answer as one possible solution. – Steve Jessop Dec 12 '13 at 03:10
  • 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. – Miral Dec 12 '13 at 04:10
  • @Miral: My answer provides exactly that. – Ben Voigt Dec 12 '13 at 04:31
  • @BenVoigt: That's what it looked like to me. I do like the look of this answer the most. I'm in the middle of something else right this second but I'll get back to you shortly once I've given it a try. – Miral Dec 12 '13 at 04:39
  • Hang on, so after all of this, the question was "how do I avoid code duplication between functions", and the answer was "write a function (template) and call it from both functions"? Seems a bit anticlimactic :-) I suppose there's a twist, in that people usually ask this about const and non-const member functions, so the issue of only const references binding to temporaries doesn't arise for them and they never even wonder whether it can be solved without overloading. – Steve Jessop Dec 12 '13 at 09:47
  • I did say that it ought to be an easy question. I had just forgotten that you could take a "T const&" and pass it to a templated "U&" function and that it would remain const when doing so. – Miral Dec 12 '13 at 21:43
1

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! :)

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • This will call the wrong `operator()()` overload, and uses `const_cast` with no benefit. See my answer for the right way to do it (at least g++ accepts it, and I think it's fully Standard compliant). – Ben Voigt Dec 12 '13 at 02:41
  • @BenVoigt: I fail to see how it can "call the wrong `operator()() overload". Please do give an example. – Cheers and hth. - Alf Dec 12 '13 at 02:42
  • @Ben: that's what "require explicit specification that the argument is a temporary" means. If the documentation for the overload including `Temporary` says, "you must supply a temporary: not for example a reference to a `const` object", then it's fine to call the non-const `operator()`. Anyone who calls it like `do_something(a, with_temp, some_reference_to_const)` hasn't RTFM, they're at fault. – Steve Jessop Dec 12 '13 at 02:43
  • from the question: "(and in particular would invoke the wrong method if the functor intentionally implements both const and non-const call operators)." – Ben Voigt Dec 12 '13 at 02:44
  • @BenVoigt: the quote from the question is about getting wrong overload when calling on a temporary as a `const`. in the above, the call is on a temporary as non-`const`, precisely to avoid that problem. if you still think it's wrong, please do give a concrete example. – Cheers and hth. - Alf Dec 12 '13 at 02:46
  • @Alf: Read that section of the question again, in its full context. – Ben Voigt Dec 12 '13 at 02:47
  • @Alf: maybe it would be better to call the special overload `with_non_const` rather than `with_temp`, and document it to mean that the non-const `operator()` will be called regardless of what's passed in. It has exactly the same meaning in code of course, but might persuade Ben (and other future readers) around to your view of which is the "right" `operator()` to call. You'd call the one the caller explicitly tells you to call. – Steve Jessop Dec 12 '13 at 02:49
  • @BenVoigt: yes, I misunderstood. He is concerned about casting away `const` on a `const` actual argument, thus invoking the wrong overload. My code doesn't do that. – Cheers and hth. - Alf Dec 12 '13 at 02:49
  • @Steve: Except that it still doesn't provide any way to call the `const` version. – Ben Voigt Dec 12 '13 at 02:50
  • @BenVoigt: of course it does. You use the overload that doesn't include `with_temp`, the original `template size_t do_something( T const& a, F& f)` – Steve Jessop Dec 12 '13 at 02:51
  • @Steve: Will that infer a const type? It didn't in my testing. – Ben Voigt Dec 12 '13 at 02:51
  • @BenVoigt: yes, it infers a `const` type. – Cheers and hth. - Alf Dec 12 '13 at 02:52
  • @BenVoigt: d'oh, you're right that it won't deduce a `const` type from an rvalue (would from a const reference of course). You'd have to explicitly specify the template parameters on calling `do_something`, or call it like `do_something(a, (const MyFunctor&)MyFunctor())`. Which only sucks a bit. But maybe once there's an explicit `with_non_const` overload, the original can change to take `const F&` with no harm done. Callers passing a non-const lvalue would have to use the `with_non_const` version if they want non-const `operator()` called. – Steve Jessop Dec 12 '13 at 02:53
  • Not sure: `With_nonconst` feels very much less informative to user of code, and the problem of `const` rvalue seems a bit construed (never encountered it), but it is there. I think, if someone wants to call the function the with a `const` rvalue, then that's that someone's problem, e.g. to use explicit instantiation. The idea is to make the common cases simple, and the esoteric rare case just *possible*. – Cheers and hth. - Alf Dec 12 '13 at 02:59
  • @Steve: If you make the main implementation `const F&`, now the `with_non_const` version has nothing to delegate to. – Ben Voigt Dec 12 '13 at 03:02
  • @BenVoigt: yeah. Given your answer I'm pretty sure you know how to fix that :-) – Steve Jessop Dec 12 '13 at 03:04
  • I updated the answer with a solution more in line with my thinking about common cases should be simple, rare cases supported. :) – Cheers and hth. - Alf Dec 12 '13 at 03:10
  • I still need to read through all of this, but I'll just note that accepting a temporary is the most common case, not a rare one (since `bind` is almost always used inline). – Miral Dec 12 '13 at 04:13
  • @Miral: "temporary" != "temporary of const type" – Cheers and hth. - Alf Dec 12 '13 at 04:44
  • Possibly, but as a temporary cannot be passed to a function by non-const reference (without C++11), the distinction is kinda meaningless. – Miral Dec 12 '13 at 04:52
  • @Miral: Not exactly meaningless. Using `const_cast` on a temporary is ok, unless that temporary has `const` type, in which case it's UB. – Ben Voigt Dec 12 '13 at 05:04
  • @BenVoigt: the code being called can't know which of those applies, though, unless it gets given an extra hint parameter like in this answer. Which might work but it strikes me as ugly. – Miral Dec 12 '13 at 05:10
  • @Miral: Your requirements stated in the question don't need any `const_cast` at all, so it isn't an issue. – Ben Voigt Dec 12 '13 at 05:11
  • @BenVoigt: Not if I do it in the way suggested in your answer. This answer does use a `const_cast`, and `const_cast` was one of the alternatives I mentioned (but was not happy with) in my question. – Miral Dec 12 '13 at 05:12