Disable Implicit Casts

Consider the following simple function:

void foo(uint32_t x);

The problem with foo is that I can call it with anything which can be implicitly cast to a uint32_t with no complaints from the compiler. It is perfectly valid to call foo with a floating-point number: foo(5.9f). The compiler will happily add an implicit cast to transform the argument to a 5. Most compilers have warnings that can be turned on to catch this sort of thing, but if you are a library writer or you are working on a project where turning on these warnings would be a hassle, then -Wconversion is not a reliable option.

I want to opt-in to disabling implicit casts for a single function. To do this for foo, add the following declaration:

template <typename T>
void foo(T) = delete;

When the compiler sees a statement like foo(8.3), it searches for functions by name and it finds two valid candidates for the name foo: The templated one and the regular one. Since we have more than one available function matching the name, we have to disambiguate by the argument types. The definition template <typename T> foo can be validly instantiated by the expression foo(8.3) with T=double. The instantiated function signature foo(double) is a better match than foo(uint32_t), so the compiler decides that you mean to call the templated function. Now we ask the question: Is it legal to call this function? That’s when <tt>= delete</tt> comes in – you can not call a deleted function. Bam! No more implicit conversions allowed.</p>

Note

The question of “Is it legal to call this?” really is the last question the compiler asks. This can lead to far more surprising results in cases like this:

void bar(int x)
{ }

class base
{
private:
    void bar() = delete;
};

class derived : public base
{
public:
    derived()
    {
        bar(8);
    }
};

At first glance, the expression bar(8) in derived::derived would call the free function which accepts an integer, but it turns out only base::bar is found in the name-matching stage. The free function bar we probably intended to call is never even evaluated for argument type match. For more fun times with name lookups, check out the awesome article Guru of the Week #30: Name Lookup.

Multiple Arguments

This same technique works for multiple argument functions, too:

void baz(size_t x, double y);
void baz(const std::string& s, const std::vector<uint32_t>& v);

template <typename T0, typename T1>
void baz(T0, T1) = delete;

This works for the same reasons the single-argument foo works, except with two arguments. An interesting question is: Why do you need both T0 and T1? Why not make the definition more like:

template <typename T>
void baz(T, T) = delete;

That definition says that for a template to match that pattern, both arguments must be the same; this can lead to many unexpected consequences. Consider the expression baz(5.6, 1). The type for T is ambiguous between double and int, which is a substitution failure (not an error), so template <typename T> baz will not be matched. This leaves baz(size_t, double) and baz(const std::string&, const std::vector<uint32_t>&). There is an implicit conversion of 5.6 to a size_t and of 1 to a double, so baz(size_t, double) is selected. The lesson? Use a separate template parameter for each function argument you want to disable implicit casting for.

So what if you have arguments where you do not care if there is an implicit cast? You can do that, too!

void fizz(uint32_t x, const std::string& s);

template <typename T>
void fizz(T, const std::string&) = delete;

Swashbuckling with Variadic Templates

Let’s say I have a whole host of functions:

void* buzz(const something& s);
double buzz(int, double, float, long long);
int buzz(const char*, int);
long long buzz(unsigned int, long long);

Yuck! All these functions have different return types and different numbers of arguments (arity). I do not want any implicit conversions happening on any of them. Now, I could use the template trick for each arity of buzz, but there are two major problems with doing that. If someone comes in and adds a function of arity 3 (say buzz(int, double, void*)), we will not have a function deletion covering it. More importantly, writing = delete so frequently is a lot of work (and a good C++ pirate is lazy). Here, variadic templates come to the rescue!

template <typename... T>
void buzz(T...) = delete;

Now, any attempted call to a function named buzz which does not exactly match a non-template option will show up as a deleted function. You probably noticed that we do not care about the return type at all. This means that expressions needing a specific return type from the result will fail, but that does not matter, since the expression is invalid anyway due to the = delete business. That means we can wrap this whole deal up in a quite nice-looking macro:

#define DISABLE_IMPLICIT_CASTS(func_name) \
    template <typename... T>              \
    void func_name(T...) = delete

Then again, the = delete looks fine to me.