4

In a function I need to distinguish among lvalue and rvalue references, so the obvious path is to overload:

void myfunc(A&& a);
void myfunc(const A& a);

This has exactly the desired behaviour, with well defined type and implicit conversions. However there's too much code duplication and I'd prefer to encapsulate the relevant decisions inside, keeping only a single function, therefore passing by universal reference might be an option:

template <typename A>  void myfunc(A&& a);

However this has the unfortunate drawback that now any object can be passed as first parameter, so one might impose constraints via enable_if:

template <typename T, class  = typename enable_if<
    is_same<typename remove_const<typename    remove_reference<T>::type>::type, A>::value,
    T>::type>   
void myfunc( T&&  a);

This almost seems to do the job, but (I guess by templatizing already) we have lost a nice property of overloads which can trigger implicit conversion constructors to type A (say from type C parameters). Overloads are NOT an option since some functions might have 3 or more A&& parameters, no intention to deal with combinatorial explosion. Can implicit conversions be somehow recovered? Of course one workaround might be e.g. to add other allowed parameter types for A, and perform any needed conversion in the main function, but this is intrusive, ugly, explicits implicits and generates confusion (the original C parameter might be an lvalue [reference] and yet produce an rvalue by conversion). Any better way?

T.C.
  • 133,968
  • 17
  • 288
  • 421
Quartz
  • 141
  • 6

1 Answers1

5

This is why std::is_convertible exists:

template <typename T, 
          class = typename enable_if<is_convertible<T, A>::value>::type>   
void myfunc( T&&  a);
TartanLlama
  • 63,752
  • 13
  • 157
  • 193
  • Thanks a lot, it seems really clean! However I still do not fully understand it, why is this not just equivalent to explicitly allowing for type C (say combining multiple is_same constraints)? What exactly triggers the conversion, and why the function isn't (?) just instantiated with an unsupported C argument (as is the case with no constraint at all)? Also, why decay? I don't want type A, just references... – Quartz Feb 15 '16 at 14:31
  • @Quartz Essentially, this allows any `T` so long as `T` can be implicitly-converted to `A`. Regarding the decay, that was silly. – TartanLlama Feb 15 '16 at 14:38
  • Odd, with decay it worked fine, without it doesn't at all (enable_if fails). Furthermore I had no problems with imposing constraints per se (to implicitly convertibles included), the issue is about enabling/disabling implicit conversions in that context. That combination (convertible+decay) seemed to work nicely on my g++ for some reason, but as long as no reliable rationale is presented it can't be trusted to behave differently than an unconstrained version (in that respect, of course). – Quartz Feb 15 '16 at 15:24
  • @Quartz What fails for you? Likely this will behave just like an unconstrained version, but you get the benefit of having your type requirements documented in the function signature. – TartanLlama Feb 15 '16 at 15:32
  • @Quartz When not using `std::decay`, you should _forward_ all instances of your object as so: `std::forward(t)`. This allows your object to maintain the type-semantics it was passed in with. The reason your `std::decay` version works is b/c it erases such semantics away. (By semantics, I mean references or temporary tags: `int`, `const int&`, `int&&` all behave as `int` when you decay it. This is why T.C. suggested removing it, conversion functions are oft specialized for these special reference types.) – Brian Rodriguez Feb 15 '16 at 16:40
  • @TartanLlama 1) (no decay): template deduction/substitution fails in the enable_if when passing an lvalue! rvalue+implicit conversion seems ok 2) the unconstrained version simply instantiated the template with type C without converting; not as desired (but now it somehow works, damn!). Overloads forced conversion instead. With is_convertible the requirement is in the template definition but might not be in the instantiated function signature (and thus I don't expect it to perform the conversion). My understanding of the differences w.r.t. overloads however is limited, iirc it's a tricky topic. – Quartz Feb 15 '16 at 16:46
  • @BrianRodriguez: using _forward_ already. See previous comment. – Quartz Feb 15 '16 at 17:26