Jan Gabriel
Jan Gabriel

Reputation: 1206

How to safely allow single argument templated constructor without using explicit?

I have a Value class which I use for wrapping and converting basic types.

class Value {
 public:
  template <typename T>
  Value(const T &value) : value_(StringMaker<T>(value)) {} // Not marked explicit

  // Example -> Convert to String
  std::string AsString() const;

  // Example -> Convert to Int
  int AsInt(bool *err_flag = nullptr);

 private:
  const std::string value_;

  // Helper method to convert values to a string
  template <typename T>
  std::string StringMaker(const T &value);
};

I have other classes which use the Value class e.g. the class Foo:

struct Foo {
  explicit Foo(const Value &value) : bar(value) {}
  const Value bar;
};

With usage as follows:

  // Normal use
  std::cout << "Value converted to \"std::string\": " << Value(100).AsString()
            << std::endl;

  // Normal use via Foo class
  std::cout << "Value converted to \"std::string\" via \"Foo\": "
            << Foo(100).bar.AsString() << std::endl;

and output:

Value converted to "std::string": 100
Value converted to "std::string" via "Foo": 100

I would like to make use of a implicit constructor for Value, but with cpplint and Cppcheck I get the warning:

Single-parameter constructors should be marked explicit. [runtime/explicit] [5]

If I add explicit to the Value constructor I get additional errors like:

error: no matching function for call to ‘Foo::Foo(int)’
             << Foo(100).bar.AsString() << std::endl;

Thus, is there a safe way to keep the static analysis tools happy and also use implicit conversion?

Here is a working example with additional use-cases.

Upvotes: 0

Views: 804

Answers (1)

463035818_is_not_an_ai
463035818_is_not_an_ai

Reputation: 122830

Thus, is there a safe way to keep the static analysis tools happy [...]

Yes, make the converting constructor explicit ;)

You can only disable or ignore the warning or make the constructor explicit. Though the warning exists for a reason, implicit conversions can be a source of confusion. Because Values constructor is a template, you can pass any parameter to Foos constructor too. Latest when you want to add a second constructor for Foo this can be confusing, for example:

struct Foo {
  explicit Foo(const Value &value) : bar(value) { /* do something */}
  explicit Foo(int x): bar(x) { /* do something else */}
  const Value bar;
};

Then Foo(100) suddenly calls a different constructor, which instead of something does something else. Hence I suggest to make contructor of Value explicit.

Thus, I cannot do explicit with only Foo(100)

explicit means just that: You have to explicitly call the constructor. Foo(100) does not explicitly call Values constructor, but Foo(Value(100)) does. The sequence of conversions is exactly the same, but the code is more clear.

So instead of:

std::cout << Foo(100).bar.AsString() << std::endl;

write

std::cout << Foo(Value(100)).bar.AsString() << std::endl;

Alternatively, find a way to silence the warning. In rare cases implicit conversions are just right, but you need to be aware of the implications.

Upvotes: 2

Related Questions