1

The High Integrity C++ Standards suggest that rvalue arguments to functions can be deleted thus preventing implicit conversions.

http://www.codingstandard.com/rule/8-3-4-define-delete-functions-with-parameters-of-type-rvalue-reference-to-const/

I've found that the behaviour for primitives and user-defined types is very different.

struct A { };

struct B { B(const A& ) {} };

template <class T>
void foo(const T&&) = delete;  // 1 - deleted rvalue overload. const intentional.

void foo(B) {}                 // 2

void foo(int) {}               // 3

int main(int argc, char* argv[])
{
  A a;
  foo(a);   // This resolves to 2
  foo(3.3); // This resolves to 1
  foo(2);   // This resolves to 3 (as expected).
}       

Why does a deleted rvalue overload prevent an implicit conversion to int but not from one user-defined type to another?

jbcoe
  • 3,611
  • 1
  • 30
  • 45
  • MM pointed out that my code example confuses matters. – jbcoe May 16 '17 at 06:48
  • The second half of this sentence is not accurate either "The High Integrity C++ Standards suggest that rvalue arguments to functions can be deleted thus preventing implicit conversions.". RValue overloads can be deleted but HIPCC does not suggest that this can prevent implicit conversions. – jbcoe May 16 '17 at 07:12

3 Answers3

2

The High Integrity C++ Standards suggest that rvalue arguments to functions can be deleted thus preventing implicit conversions.

No, only a forwarding reference overload disables ICS (Implicit Conversion Sequence) for all other overloads in the overload set. Make it a forwarding reference, and see ICS disabled (Coliru Link)

template <class T>
void foo(const T&&) = delete;  // introduces a qualification match

The above code adds a qualification match to the overload. Thus, ICS is still in play.

Why foo(3.3) failed is because 3.3 is an prvalue of type double which will match better with the rvalue overload than converting to int. Because qualification match is ranked better than a conversion match

WhiZTiM
  • 21,207
  • 4
  • 43
  • 68
  • Declaring `void foo(const B&&) = delete;` Does prevent implicit conversion. My code example confuses matters though. I'll post a follow up question. – jbcoe May 16 '17 at 07:14
  • 1
    @jbcoe, yes, deleting a `const` *rvalue* overload for a particular type, also disables ICS for that type. But deleting a *forwarding reference* overload, disables ICS for all the entire overload set. – WhiZTiM May 16 '17 at 07:25
  • Thanks. Why is the behaviour different for functions and function templates? – jbcoe May 16 '17 at 07:30
  • Deleting a forwarding reference is too aggressive as it prevents invocation with a non-const lvalue reference. – jbcoe May 16 '17 at 08:06
  • posted follow-up question: http://stackoverflow.com/questions/43995946/how-can-i-prevent-implicit-conversions-in-a-function-template – jbcoe May 16 '17 at 08:16
  • @jbcoe, *function templates* do not convert invoke any conversion operations, at most they apply type qualifications. *functions* on the other hand may invoke conversions. And overload resolution takes these into account in selecting the final *function specialization* used for a particular call with its argument – WhiZTiM May 16 '17 at 08:28
1

There are 3 possible overloads

  • 1 is viable.
  • 2 is viable
  • 3 isn't

2 is better match (template (non exact match) vs regular method (with one user define conversion)).

You may look at http://en.cppreference.com/w/cpp/language/overload_resolution to see a complete set of rules needed

Jarod42
  • 203,559
  • 14
  • 181
  • 302
1

In your code, there is no difference in treatment between user-defined types and primitive types. The difference between the behaviour of these two lines:

foo(a);
foo(3.3);

is that a is an lvalue, and 3.3 is an rvalue. The rvalue argument matches your overload 1 (which only accepts rvalues), the lvalue argument does not.

If you try to invoke foo<A> with an rvalue argument it will also match 1 and fail, e.g. foo(A{});.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • You are right. I'm a little puzzled by this. Why does an implicit conversion not also match the rvalue overload? (Perhaps this is worth asking in a separate question?) – jbcoe May 16 '17 at 06:47
  • 1
    @jbcoe In overload resolution, templates are instantiated according to the arguments provided. *Then*, overload resolution occurs amongst the instatantions and non-template functions (which involves considering implicit conversion sequences and so on). – M.M May 16 '17 at 07:36
  • For example, in `foo(a)`, the overload set is `foo(B)` and `foo(int)` - there was no possible template instantiation for `a` as argument. But for `foo(3.3)` the overload set is `foo(const double&&)`, `foo(B)` and `foo(int)`. The ranking process selects the first of those three because direct reference binding is ranked ahead of a floating-to-int conversion – M.M May 16 '17 at 07:41
  • posted follow-up question: http://stackoverflow.com/questions/43995946/how-can-i-prevent-implicit-conversions-in-a-function-template – jbcoe May 16 '17 at 08:16