4

How can I define a function template to prevent implicit conversions?

It seems I can prevent implicit conversions using non-template functions but not using function templates.

Defining a forwarding reference function template as = delete is too aggressive as it prevents invocation with non-const lvalue references.

Defining an function template with a const rvalue argument as =delete [1] does not prevent implicit conversions.

Defining an rvalue overload for a specific type as =delete works but I'd like to accomplish this with templates.

Minimal code example:

struct A {};

struct B {
  B() = default;

  B(const A&) {}
};

// Delete const rvalue reference.
template <class T>
void t_no_rvalue(const T&&) = delete; // 1

void t_no_rvalue(const B&) {}         // 2


// Delete forwarding reference.
template <class T>
void t_no_fwd_ref(T&&) = delete;     // 3

void t_no_fwd_ref(const B&) {}       // 4


// (non-template) Delete const rvalue reference.
void no_rvalue(const B&&) = delete;  // 5

void no_rvalue(const B&) {}          // 6


int main(int argc, char* argv[]) {
  A a;
  B b;

  // Undesired behaviour, implicit conversion allowed.
  t_no_rvalue(a);   // resolves to 2
  t_no_rvalue(b);   // resolves to 2

  // Undesired behaviour, invocation with non-const reference disallowed.
  t_no_fwd_ref(a);  // resolves to 3
  t_no_fwd_ref(b);  // resolves to 3

  // Desired behaviour.
  no_rvalue(a);     // resolves to 5
  no_rvalue(b);     // resolves to 6
}

My real-world use case is hashing of variants where implicit conversion of a variant sub-type back to the variant-like type will cause infinite recursion if the hash function is not specialized for all the variant constituents. The sample code above is clearer though.

[1] Attempted in Why can I prevent implicit conversions for primitives but not user-defined types? but with a broken code example.

Community
  • 1
  • 1
jbcoe
  • 3,611
  • 1
  • 30
  • 45
  • 2
    It's not clear to me what you're asking - i.e. why does `no_rvalue` not solve your problem? You mention you want a template, but what would the template parameters be? It might help if you provide a `main()` showing more examples of what should or shouldn't compile. – M.M May 16 '17 at 08:24
  • 1
    Rather than subjecting yourself with dealing with ICS hullabaloo as regards to dealing with *functions*. Why don't you require your users to specialize a *class-template* instead of overload a *function*. I think you should explore the use of *class-template* specializations or some other machinery. Think of how `std::hash` was designed. – WhiZTiM May 16 '17 at 08:32
  • 1
    `explicit B(const A&){}` ?? – Praveen May 16 '17 at 08:35
  • @M.M. no_rvalue has the desired behaviour but I don't want the user to specify two non-template functions. I'd rather provide one deleted version and have the user specify the one they will implement. – jbcoe May 16 '17 at 08:35
  • @Praveen That stops all implicit conversions which is more aggressive than I want. I also may not be able to change the explicit nature of the class constructor (std::variant for instance). – jbcoe May 16 '17 at 08:37
  • @WhiZTiM. I can use class-template specializations but that subjects users to (possibly avoidable) boilerplate. – jbcoe May 16 '17 at 08:38

1 Answers1

1

The following overload will prevent implicit conversions:

template <class T>
void no_conversions(T) = delete; // 7

void no_conversions(const B&) {} // 8

and leads to:

// Requested behaviour.
no_conversions(a); // resolves to 7
no_conversions(b); // resolves to 8

A value-overload poisons the overload set for implicit conversions as it will be an exact match.

Edit:

template <class T>
void no_conversions(const T&) = delete; // 9

void no_conversions(const B&) {}        // 10

works just as well.

jbcoe
  • 3,611
  • 1
  • 30
  • 45
  • 1
    The latter case, `const T&`, is preferable for poisoning because it doesn't cause any unrelated errors with potentially deleted copy or move constructors of `T`. – Kerrek SB Sep 19 '17 at 18:09
  • Thanks, nice to pick one of the approaches. Do you have an example of the confusing errors? – jbcoe Sep 19 '17 at 21:31
  • 1
    For example, consider a class like `struct X { X(X&&) = delete; /* ... */}`. Using the by-value version, GCC will unnecessarily complain that the copy constructor is deleted: https://wandbox.org/permlink/TTSATJZNr53RJUKQ. Clang is a bit better and puts the relevant error first (https://wandbox.org/permlink/TTSATJZNr53RJUKQ), but you still clutter up the error output with irrelevant things. By using the reference version you avoid considering copies altogether and focus only on the thing you actually mean. – Kerrek SB Sep 20 '17 at 13:31