I have this class
struct foo
{
explicit foo(const std::uint32_t& x, const std::uint32_t& y);
};
and a method
int main()
{
std::int32_t x = -1;
std::int32_t y = -1;
foo f(x, y);
}
On my compiler (MSVC2012), this compiles and runs with the values x and y wrapped around to unsigned types. I was not expecting this, but was expecting a compile error due to mismatched types.
What am I missing?
You're out of luck, the standard does allow implicit conversion of signed to unsigned via the creation of an anonymous temporary for an argument passed by constant reference.
(Note this is not true for a non-constant reference).
If you're using C++11, the best thing to do is to delete the constructor using
foo(const std::int32_t& x, const std::int32_t& y) = delete;
Pre C++11 you could make this constructor private and not define it. Rather like the old-fashioned not-copyable idioms.
MSVC2012 is a sort of half-way house C++03 / C++11 compiler. It implements some C++11 features but not others. Unfortunately deletion of constructors is one of the features it does not support so the privateisation approach is the best method available to you.
Actually, you should use the new brace-initialization syntax foo f{x, y} that will at least emit a warning. After that you can configure your compiler to treat warnings as errors and handle them accordingly, as good code should usually get rid of warnings too (because if you wanted the conversion to happen, you should have used an explicit cast).
explicit does not prevent implicit conversion with the constructor arguments (which clearly takes place here when binding the references); it prevents implicit construction.
void bar(foo);
int main()
{
foo f({0, 0}); // doesn't matter that the arguments are implicitly converted
bar({0, 0}); // error - implicit conversion required here to pass a foo
bar(f); // crucially, ok because argument requires no conv. construction
}
Related
This question already has an answer here:
Why does float argument fit to int function parameter?
(1 answer)
Closed 5 years ago.
class foo {
public:
explicit foo(int) { std::cout<<"int constructor"; }
};
int main() {
foo f(0.1);
return 0;
}
I thought explicit keyword was being used to prevent unwanted type conversions, but the above code still works and outputs "int constructor". Why? And how to prevent that in this case?
You're confusing some concepts here.
explicit prevents implicit constructor calls, not constructor calls with implicit parameter type conversions.
Unfortunately the solution is not straightforward. It's not quite newbie-friendly and may require some googling to understand.
You could expicitly delete the constructors for floating-point types: foo(float) = delete; and so on. Then foo f(0.1); will cause 'use of deleted function' error. But you won't be able to do foo f(1l) either, the compiler will complain about 'ambiguous overloads'.
The proper way is following:
class foo
{
public:
foo(int) {std::cout << "int constructor\n";}
template <typename T,
typename = std::enable_if_t<std::is_floating_point_v<T>>>
foo(T) = delete;
};
It's similar to deleting overloads for each floating-point type (as described above), but due to SFINAE, the deleted overload won't be considered for non-floating-point types.
An explicit constructor prevents implicit conversion into your type, from the type taken by the constructor. So in this case, it prevents implicit conversion from int into foo. So:
foo f = 1;
Will not compile. However, this is explicit construction:
foo f(1);
This, of course will work. In your case, you passed a double instead of an int. But there's an implicit conversion from double to int built into the language, that's why it compiles. In other words, your problem is that this compiles:
int x = 0.1;
However, compiling with -Wconversion on gcc (and I believe clang) will raise a warning for this, and if you are compiling with -Werror (which you should), it will turn this into a compilation error. I suspect MSVC has a similar error if you are working with that compiler.
Keyword explicit enforces a compile-time error with message as conversion from ‘int’ to non-scalar type ‘foo’ requested, when you try to do an implicit conversion like this :foo f = 1;. That's all it's expected to do.
Why does it allow float value 0.1 is answered here.
Futhermore if you want to prevent this behavior, use this line of code:
foo(double) = delete;
then you would get an error: use of deleted function ‘foo::foo(double)’ while passing a float/double value.
One of the stated advantages of initializer list syntax is that it improves type safety by prohibiting narrowing conversions:
int x {7.9}; // error: narrowing
int y = 7.9; // OK: y becomes 7. Hope for a compiler warning
However, AFAIK there is no way to enforce the same check in a subsequent assignment:
int z {7};
z = 7.9; // allows narrowing, and no other syntax available
Why is type safety during initialization given greater attention by the language design than type safety during assignment? Is narrowing in assignments less likely to cause bugs and/or harder to detect?
If x is an int variable, then
x = 7.9;
must continue working in C++11 and later for the reason of backward compatibility. This is the case even if x was brace-initialized. A brace-initialized int variable has the same type as a non-brace-initialized int variable. If you passed x by reference to a function,
void f(int& r);
f(x);
then in the body of f there would be no way to "tell" whether the object had been brace-initialized, so the language cannot apply different rules to it.
You can prevent narrowing conversions yourself by doing this:
x = {7.9}; // error
You could also try to take advantage of the type system by creating an int "wrapper" type that disallowed narrowing assignments:
struct safe_int {
template <class T> safe_int(T&& x) : value{std::forward<T>(x)} {}
template <class T> safe_int& operator=(T&& x) {
value = {std::forward<T>(x)}; return *this;
}
operator int() const { return value; }
int value;
};
Unfortunately this class cannot be made to behave like an actual int object under all circumstances.
Why is type safety during initialization given greater attention by
the language design than type safety during assignment?
No that's not the reason here, it's the list initialization that's giving you the error, for example, this would error too:
int x = {7.8};
And that is because narrowing is not allowed in list initialization, as per [dcl.init]:
If the initializer-clause is an expression and a narrowing conversion
is required to convert the expression, the program is ill-formed.
{} prevent convertion type e.g double to int
but
() can do this, only can display warning about conversion
you can to write like this
int z(7.9) // == 7
you can use brackets when you are sure, that in class don't exist constructor with std::initializer_list, because he would be execute or you can delete that constructor.
I have a Flags class that behaves similarly to std::bitset that is replacing bitpacked integers in an older codebase. To enforce compliance with the newer class, I want to disallow implicit conversion from int types.
enum class Flag : unsigned int {
none = 0,
A = 1,
B = 2,
C = 4,
//...
};
class Flags {
public:
Flags();
Flags(const Flag& f);
explicit Flags(unsigned int); // don't allow implicit
Flags(const Flags&);
private:
unsigned int value;
};
I would like to allow implicit construction and assignment only from the Flag and Flags types. However, I would still like some function calls that take a Flags parameter to accept a literal 0, but not other integers:
void foo(const Flags& f);
foo(Flags(0)); // ok but ugly
foo(1); // illegal because of explicit constructor
foo(0); // illegal, but I want to allow this
Is this possible? To allow 0 to be implicitly converted while disallowing other values?
One approach:
Add a constructor which takes void*.
Since literal 0 is implicitly convertible to a void* null pointer, and literal 1 isn`t, this will give the desired behavior indicated. For safety you can assert that the pointer is null in the ctor.
A drawback is that now your class is constructible from anything implicitly convertible to void *. Some unexpected things are so convertible -- for instance prior to C++11, std::stringstream was convertible to void*, basically as a hack because explicit operator bool did not exist yet.
But, this may work out fine in your project as long as you are aware of the potential pitfalls.
Edit:
Actually, I remembered a way to make this safer. Instead of void* use a pointer to a private type.
It might look like this:
class Flags {
private:
struct dummy {};
public:
Flags (dummy* d) { ... }
...
};
The literal 0 conversion will still work, and it's significantly harder for some user-defined type to accidentally convert to Flags::dummy * unintentionally.
Can anyone explain why does non-single parameter constructor marked as explicit compile?
As far as I understand this is absolutely useless keyword here, so why does this compile without error?
class X
{
public:
explicit X(int a, int b) { /* ... */}
};
In C++03, and in this particular case, it makes no sense for a two parameter constructor to be marked explicit. But it could make sense here:
explicit X(int i, int j=42);
So, marking a two parameter constructor with explicit does not have to be an error.
In C++11, this use of explicit would prevent you from doing this:
X x = {1,2};
Not entirely true.
In C++11, constructors with multiple arguments can be implicitly converted using brace initialisation.
How does constructor conversion work?
#include <iostream>
using namespace::std;
class One {
public:
One() { cout<<"One"<<endl;}
};
class Two {
public:
Two(const One&) {cout<<"Two(const One&)"<<endl;}
};
void f(Two) {cout<<"f(Two)"<<endl;}
int main() {
One one;
f(one);
}
produces the output
One
Two(const One&)
f(Two)
Any constructor that can be called with a single argument is considered an implicit conversion constructor. This includes simple 1-argument cases, and usage of default arguments.
This conversion is considered in any context that wants X and provided Y, and Y has such implicit conversion possibility. Note that a plenty of other, built-in conversions also play as a mix (like adjusting const-ness, integral and fp promotions, conversions, etc.) The rule is that at most one "user defined" implicit conversion is allowed in the mix.
In some cases it may be quite surprising, so the general advice is to make any such ctors explicit. That keyword makes the conversion possible but not implicitly: you must use T() syntax to force it.
As an example consider std::vector that has a ctor taking size_t, setting the initial size. It is explicit -- otherwise your foo(vector<double> const& ) function could be mistakenly called with foo(42).
It's right result. Since constructor is not explicit - implicit conversion works (here One is implicitly converted to Two).
one is created, then when passed to f converted to Two.
What the Two(const One&) {cout<<"Two(const One&)"<<endl;} constructor means is that you're allowed to construct a Two value at any time - implicitly - from a One. When you call f(one) it wants a Two parameter, it's given a One, so the compiler puts 2 and 2 together and says "I'll make a temporary Two from the One and complete the call to f()"... everyone will be happy. Hurray!
Compiler has to create copy of Two instance on stack. When you call f() with argument which is object of class One (or any other) compiler looks to definition of class Two and tries to find constructor which takes One(or any other) object(or reference) as an argument. When such constructor has been found it constructs object using it. It's called implicit because compiler do it without your interference.
class Foo {
public:
Foo(int number) {cout<<"Foo(int number)"<<endl;}
};
void f(Foo) {cout<<"f(Foo)"<<endl;}
int main() {
f(24);
} ///:~
Output will be:
Foo(int number)
f(Foo)