10

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?

futlib
  • 2,107
  • 3
  • 21
  • 26
  • 1
    what if Draw was overloaded with a string version later? – ratchet freak Mar 15 '13 at 15:56
  • 1
    per @Dave Rager's answer, I don't think this will compile on all compilers. See my comment on his answer. Apparently, according to the c++ standard, you can't chain implicit conversions like this. You can only do one conversion and no more. – Jonathan Henson Mar 15 '13 at 17:44
  • OK sorry, didn't actually compile this. Updated the example and it's still horrible, IMO. – futlib Mar 16 '13 at 06:56

3 Answers3

24

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:

Josh Kelley
  • 10,991
  • 7
  • 38
  • 50
3

This is more of a comment than an answer but too big to put in a comment.

Interestingly, g++ does not let me do that:

#include <iostream>
#include <string>

class Texture {
        public:
                Texture(const std::string& imageFile)
                {
                        std::cout << "Texture()" << std::endl;
                }
};

class Renderer {
        public:
                void Draw(const Texture& texture)
                {
                        std::cout << "Renderer.Draw()" << std::endl;
                }
};

int main(int argc, char* argv[])
{
        Renderer renderer;
        renderer.Draw("foo.png");

        return 0;
}

Produces the following:

$ g++ -o Conversion.exe Conversion.cpp 
Conversion.cpp: In function ‘int main(int, char**)’:
Conversion.cpp:23:25: error: no matching function for call to ‘Renderer::Draw(const char [8])’
Conversion.cpp:14:8: note: candidate is: void Renderer::Draw(const Texture&)

However, if I change the line to:

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

It will perform that conversion.

Dave Rager
  • 874
  • 6
  • 9
  • That is an interesting "feature" in g++ indeed. I suppose that is either a bug which only checks one type deep instead of going recursively down as far as possible at compile time to generate the correct code, or there is a flag that needs to be set in your g++ command. – Jonathan Henson Mar 15 '13 at 17:37
  • 1
    http://en.cppreference.com/w/cpp/language/implicit_cast it seems that g++ is following the standard quite strictly. It is Microsoft or Mac's compiler that is being a bit too generous with the O.P.'s code. Especially telling is the statement: "When considering the argument to a constructor or to a user-defined conversion function, only one standard conversion sequence is allowed (otherwise user-defined conversions could be effectively chained). " – Jonathan Henson Mar 15 '13 at 17:42
  • Yeah, I just threw the code together to test out some of the `gcc` compiler options (which it doesn't look like there are any to address this particular case). I didn't look much further into it (I'm supposed to be working :-) but given `gcc`'s adherence to the standard and the use of the `explicit` keyword a compiler option was probably deemed unnecessary. – Dave Rager Mar 15 '13 at 17:58
  • Implicit conversions are not chained, and a `Texture` should probably not be constructed implicitly (according to guidelines in other answers), so a better call-site would be `renderer.Draw(Texture("foo.png"));` (assuming it works as I expect). – Blaisorblade Nov 16 '15 at 22:39
3

It's called implicit type conversion. In general it's a good thing, as it inhibits unnecessary repetition. For example, you automatically get a std::string version of Draw without having to write any extra code for it. It can also aid in following the open-closed principle, as it lets you extend Renderer's capabilities without modifying Renderer itself.

On the other hand, it's not without drawbacks. It can make it difficult to figure out where an argument is coming from, for one thing. It can sometimes produce unexpected results in other cases. That's what the explicit keyword is for. If you put it on the Texture constructor it disables using that constructor for implicit type conversion. I'm not aware of a method to globally warn on implicit type conversion, but that doesn't mean a method doesn't exist, only that gcc has an incomprehensibly large number of options.

Karl Bielefeldt
  • 146,727
  • 38
  • 279
  • 479