C++ Coding Style – Is Relying on Implicit Argument Conversion Dangerous?

ccoding-styleconstructionreadabilitytype conversion

C++ has a feature (I cannot figure out the proper name of it), that automatically calls matching constructors of parameter types if the argument types are not the expected ones.

A very basic example of this is calling a function that expects a std::string with a const char* argument. The compiler will automatically generate code to invoke the appropriate std::string constructor.

I'm wondering, is it as bad for readability as I think it is?

Here's an example:

class Texture {
public:
    Texture(const std::string& imageFile);
};

class Renderer {
public:
    void Draw(const Texture& texture);
};

Renderer renderer;
std::string path = "foo.png";
renderer.Draw(path);

Is that just fine? Or does it go too far? If I shouldn't do it, can I somehow make Clang or GCC warn about it?

Best Answer

This is referred to as a converting constructor (or sometimes implicit constructor or implicit conversion).

I'm not aware of a compile-time switch to warn when this occurs, but it's very easy to prevent; just use the explicit keyword.

class Texture {
public:
    explicit Texture(const std::string& imageFile);
};

As to whether or not converting constructors are a good idea: It depends.

Circumstances in which implicit conversion makes sense:

  • The class is cheap enough to construct that you don't care if it's implicitly constructed.
  • Some classes are conceptually similar to their arguments (such as std::string reflecting the same concept as the const char * it can implicitly convert from), so implicit conversion makes sense.
  • Some classes become a lot more unpleasant to use if implicit conversion is disabled. (Think of having to explicitly invoke std::string every time you want to pass a string literal. Parts of Boost are similar.)

Circumstances in which implicit conversion makes less sense:

  • Construction is expensive (such as your Texture example, which requires loading and parsing a graphic file).
  • Classes are conceptually very dissimilar to their arguments. Consider, for example, an array-like container that takes its size as an argument:
    class FlagList
    {
        FlagList(int initial_size); 
    };

    void SetFlags(const FlagList& flag_list);

    int main() {
        // Now this compiles, even though it's not at all obvious
        // what it's doing.
        SetFlags(42);
    }
  • Construction may have unwanted side effects. For example, an AnsiString class should not implicitly construct from a UnicodeString, since the Unicode-to-ANSI conversion may lose information.

Further reading:

Related Topic