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.
Related
From §4.2.7 of the proposal http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0847r7.html#pathological-cases
It said that:
These are even more unlikely to be actually useful code. In this example, B is neither convertible to A nor int, so neither of these functions is even invocable using normal member syntax. However, you could take a pointer to such functions and invoke them through that pointer. (&B::bar)(42) is valid, if weird, call.
However, It does not specify whether the standard does not allow explicit object parameter of type-of-self implicitly convertible to particular another type.
struct A { };
struct B {
void foo(this A&);
void bar(this int);
};
Does that mean that:
struct A { };
struct B {
operator A() const noexcept;
void foo(this A);
};
// ...
// '&B::foo' is of type function pointer not pointer to member function
// because it uses explicit object parameter.
(&B::foo)(A{});
(&B::foo)(B{});
B{}.foo(); // will work?
will work?
In another case, here is a lambda. Since the type of the lambda is unutterable and is always dependent. What about the case above? (this captureless lambda is convertible to int(*)(int, int, int))
auto fib = [](this int(* self)(int, int, int), int n, int a = 0, int b = 1) {
return n == 0 ? a : n == 1 ? b : self(n - 1, b, a + b);
};
Given that:
Non-member functions, static member functions, and explicit object member functions match targets of function pointer type or reference to function type. Non-static Implicit object member functions match targets of pointer-to-member-function type. ([over.match.viable] §12.2.3)
In all contexts, when converting to the implicit object parameter or when converting to the left operand of an assignment operation only standard conversion sequences are allowed. [Note: When converting to the explicit object parameter, if any, user-defined conversion sequences are allowed. - end note ] ([over.best.ics] §12.2.4.2)
For your first question:
struct A { };
struct B {
operator A() const noexcept;
void foo(this A);
};
B{}.foo(); // will work?
Yes. Candidate lookup will find B::foo, which more or less evaluates as foo(B{}), which is valid due to the conversion function. This is explicitly called out in the note you cited, in [over.ics.best]/7:
[Note 5: When converting to the explicit object parameter, if any, user-defined conversion sequences are allowed. — end note]
This one, I'm actually not entirely sure about:
auto fib = [](this int(* self)(int, int, int), int n, int a = 0, int b = 1) {
return n == 0 ? a : n == 1 ? b : self(n - 1, b, a + b);
};
It seems exceedingly unlikely to be useful and you should probably never do this, so I don't know that it matters whether or not it's actually valid. But also am not sure what it means for examples like this:
struct C {
C(auto);
void f();
};
auto lambda = [](this C c) { c.f(); }; // OK?
If this is convertible to a function pointer, what function pointer type exactly? If it's void(*)(), then which C are we invoking f() on? So it'd kind of have to be void(*)(C), in which case the fib example definitely does not work because it's impossible to spell the function pointer type non-generically in a way that matches.
I have a class that has both implicit conversion operator() to intrinsic types and the ability to access by a string index operator[] that is used for a settings store. It compiles and works very well in unit tests on gcc 6.3 & MSVC however the class causes some ambiguity warnings on intellisense and clang which is not acceptable for use.
Super slimmed down version:
https://onlinegdb.com/rJ-q7svG8
#include <memory>
#include <unordered_map>
#include <string>
struct Setting
{
int data; // this in reality is a Variant of intrinsic types + std::string
std::unordered_map<std::string, std::shared_ptr<Setting>> children;
template<typename T>
operator T()
{
return data;
}
template<typename T>
Setting & operator=(T val)
{
data = val;
return *this;
}
Setting & operator[](const std::string key)
{
if(children.count(key))
return *(children[key]);
else
{
children[key] = std::shared_ptr<Setting>(new Setting());
return *(children[key]);
}
}
};
Usage:
Setting data;
data["TestNode"] = 4;
data["TestNode"]["SubValue"] = 55;
int x = data["TestNode"];
int y = data["TestNode"]["SubValue"];
std::cout << x <<std::endl;
std::cout << y;
output:
4
55
Error message is as follows:
more than one operator "[]" matches these operands:
built-in operator "integer[pointer-to-object]" function
"Setting::operator[](std::string key)"
operand types are: Setting [ const char [15] ]
I understand why the error/warning exists as it's from the ability to reverse the indexer on an array with the array itself (which by itself is extremely bizarre syntax but makes logical sense with pointer arithmetic).
char* a = "asdf";
char b = a[5];
char c = 5[a];
b == c
I am not sure how to avoid the error message it's presenting while keeping with what I want to accomplish. (implicit assignment & index by string)
Is that possible?
Note: I cannot use C++ features above 11.
The issue is the user-defined implicit conversion function template.
template<typename T>
operator T()
{
return data;
}
When the compiler considers the expression data["TestNode"], some implicit conversions need to take place. The compiler has two options:
Convert the const char [9] to a const std::string and call Setting &Setting::operator[](const std::string)
Convert the Setting to an int and call const char *operator[](int, const char *)
Both options involve an implicit conversion so the compiler can't decide which one is better. The compiler says that the call is ambiguous.
There a few ways to get around this.
Option 1
Eliminate the implicit conversion from const char [9] to std::string. You can do this by making Setting::operator[] a template that accepts a reference to an array of characters (a reference to a string literal).
template <size_t Size>
Setting &operator[](const char (&key)[Size]);
Option 2
Eliminate the implicit conversion from Setting to int. You can do this by marking the user-defined conversion as explicit.
template <typename T>
explicit operator T() const;
This will require you to update the calling code to use direct initialization instead of copy initialization.
int x{data["TestNode"]};
Option 3
Eliminate the implicit conversion from Setting to int. Another way to do this is by removing the user-defined conversion entirely and using a function.
template <typename T>
T get() const;
Obviously, this will also require you to update the calling code.
int x = data["TestNode"].get<int>();
Some other notes
Some things I noticed about the code is that you didn't mark the user-defined conversion as const. If a member function does not modify the object, you should mark it as const to be able to use that function on a constant object. So put const after the parameter list:
template<typename T>
operator T() const {
return data;
}
Another thing I noticed was this:
std::shared_ptr<Setting>(new Setting())
Here you're mentioning Setting twice and doing two memory allocations when you could be doing one. It is preferable for code cleanliness and performance to do this instead:
std::make_shared<Setting>()
One more thing, I don't know enough about your design to make this decision myself but do you really need to use std::shared_ptr? I don't remember the last time I used std::shared_ptr as std::unique_ptr is much more efficient and seems to be enough in most situations. And really, do you need a pointer at all? Is there any reason for using std::shared_ptr<Setting> or std::unique_ptr<Setting> over Setting? Just something to think about.
I have the situation where I have a class A, that provides a constructor for an integral type, and a class B that provides a implicit conversion operator for the same integral type. However, if I call a function accepting a reference to class A with an instance of class B, the compilation fails. I would have expected an implicit conversion of class B to the type accepted by the constructor of class A. Of course, if I add a constructor to A accepting class B, everything is fine. Is this behavior intended? Please checkout the example below.
#include <iostream>
class B
{
public:
B() = default;
B(const std::uint8_t &val) : mVal(val) {}
std::uint8_t get() const { return mVal; }
operator std::uint8_t() const { return mVal; }
private:
std::uint8_t mVal;
};
class A
{
public:
A() = default;
A(const std::uint8_t &val) : mVal(val) {}
// No problem if this exists
// A(const B &b) : mVal(b.get()) {}
std::uint8_t get() const { return mVal; }
private:
std::uint8_t mVal;
};
void func(const A &a)
{
std::cout << static_cast<int>(a.get()) << std::endl;
}
int main(int, char*[])
{
std::uint8_t val = 0xCE;
A a(val);
B b(val);
func(val); // fine
func(a); // fine
func(b); // error
}
There is a rule in C++ that no implicit conversion will use two user-defined conversions.
This is because such "long-distance" conversions can result in extremely surprising results.
If you want to be able to convert from anything that can convert to a uint8_t you can do:
template<class IntLike,
std::enable_if_t<std::is_convertible_v<IntLike, std::uint8_t>, bool> =true,
std::enable_if_t<!std::is_same_v<A, std::decay_t<IntLike>>, bool> =true
>
A( IntLike&& intlike ):A( static_cast<std::uint8_t>(std::forward<IntLike>(intlike)) )
{}
or you could cast your B to an uint8_t at the point you want to convert to an A.
You can do a similar thing in B where you create a magical template<class T, /*SFINAE magic*/> operator T that converts to anything that can be constructed by an uint8_t.
This obscure code:
std::enable_if_t<std::is_convertible_v<IntLike, std::uint8_t>, bool> =true,
std::enable_if_t<!std::is_same_v<A, std::decay_t<IntLike>>, bool> =true
exists to make sure that the overload is only used if the type we are converting from has the properties we want.
The first enable_if clause states that we only want things that can convert to uint8_t. The second states we don't want this constructor to be used for the type A itself, even if it passes the first.
Whenever you create a forwarding reference implicit constructor for a type, that second clause is pretty much needed or you get some other surprising issues.
The technique used is called SFINAE or Substitution Failure Is Not An Error. When a type IntType is deduced and those tests fail, there is substitution failure in those clauses. Usually this would cause an error, but when evaluating template overloads it is not an error because SFINAE; instead, it just blocks this template from being considered in overload resolution.
You are only allowed one user defined conversion when implicitly creating a object. Since func needs an A you would have a user defined conversion to turn B into a std::uint8_t and then another user defined conversion to turn that std::uint8_t into an A. What you would need is a operator A in B or a constructor in A that takes a B if you want it to happen implicitly. Otherwise you can just explicitly cast so you only need a single implicit one like
func(static_cast<std::uint8_t>(b)); // force it to a uint8_t
// or
func({b}); // make b the direct initializer for an A which will implicitly cast
// or
func(A{b}); same as #2 above but explicitly sating it
Is this behavior intended?
Yes, it is intended.
An implicit conversion sequence can have at most one user-defined conversion (constructor or conversion function).
Standard says (emphasis mine):
[over.ics.user]
A user-defined conversion sequence consists of an initial standard conversion sequence followed by a user-
defined conversion (15.3) followed by a second standard conversion sequence. ...
In order for a user defined type (a class) to be implicitly convertible to another, there must be a constructor or conversion operator directly to that type. Implicit conversion (from user defined type to another) is not possible through an intermediate type.
You could use explicit conversion instead.
I am on the task to migrate the concept of error handling in a C++ class library. Methods that previously simply returned bool (success/fail) shall be modified to return a Result object which conveys a machine readable error code and a human readable explanation (and some more which does not matter here).
Walking through thousands of lines of code is error prone, therefore I try to get the best support from the compiler for this task.
My result class has - among other member methods - a constructor that constructs the result from a code and an assignment operator for the code:
class Result
{
public:
typedef unsigned long ResultCode;
explicit Result(ResultCode code); // (1)
Result& operator=(ResultCode code); // (2)
};
Remark: I would usually use an enum class for ResultCode which would solve my problems, but this is not an option. This is because the major design objective was to use Result in different libraries, each of which shall define its own set of result codes without requiring one big header file that defines all possible result codes for all libraries. In fact, each class shall be able to define local result codes so that the list of possible result codes can be obtained from the classes header. Thus the codes cannot be enumerated in Result, they must be defined by the classes using the Result class.
To avoid implicit conversions on
return true;
Statements in the client code, the constructor has been declared explicit. But in nesting method calls, another problem occurs. Say, I have a method
bool doSomething()
{
return true;
}
Which I am using in a function that returns a Result object. I want to forward result codes of nested calls
Result doSomethingElse
{
Result result = doSomething();
return result;
}
With the current implementation of Result's assignment operator, this is not going to give me a compiler error - the boolean return value of doSomething() is implicitly converted to unsigned long.
As I have read in the C++ documentation, only constructors and conversion operators may be declared explicit.
My questions
Why is explicit not allowed for assignment operators or other methods? IMO it would make a lot of sense to allow any method to be explicit as well.
Are there other solutions to prevent implicit type conversion for the assignment operator?
Your problem isn't in the class Result: you are explicitly creating a new instance of it, after all; explicit cannot forbid it.
I don't think you can forbid the implicit promotion bool -> long.
You can work around it. One way is to make ResultCode not be an integer type. then, it could have an explicit constructor. Something like
class ResultCode
{
unsigned long m_code;
public:
explicit ResultCode(unsigned long code) : m_code(code) {}
operator unsigned long () { return m_code; }
};
would allow you to use ResultCode anywhere you can use a unsigned int and create it as ResultCode res = 5 or return ResultCode(5) but not call a function expecting a ResultCode (such as the Result constructor!) with anything which is not a ResultCode already, nor do something like return 5 if the function must return a ReturnCode.
Otherwise you can use template overloadng to 'catch' anything not being an unsigned int and force it to be an error
typedef unsigned long ResultCode;
class Result
{
ResultCode m_par;
public:
template<typename T>
Result(T param) { static_assert(false); }
template<>
Result(ResultCode par): m_par(par) {}
};
int main()
{
ResultCode a = 5; //ok
//unsigned long a = 6; //also ok
//bool a = true; //error!
//int a = 7; //also error!!
Result b(a);
}
With the current implementation of Result's assignment operator, this is not going to give me a compiler error - the boolean return value of doSomething() is implicitly converted to unsigned long.
With respect to the code you posted; it does result in an error error: no viable conversion from 'bool' to 'Result', see here.
A minimal example showing the behaviour you see in the code would be required. There is likely other constructors or type with conversion in the actual code that have a material effect on your code.
On the explicitly asked questions...
Why is explicit not allowed for assignment operators or other methods?
explicit is only allowed where implicit conversion can take place, i.e. where the compiler would attempt to generate the conversion for you (there is a special case for bool). Such conversions are constructors and the conversion (or casting operators).
Marking the constructor or conversion operator as explicit prevents the compiler from making the conversions, hence, if you require the conversion, you need to be explicit about it - as a general motivation for why this is done, it makes the code more explicit in what it does. There is a trade-off, so judicious use should be applied in both cases. The general advice is to favour explicit when in doubt.
For example;
struct Result {
Result(long src); // can be marked explicit
operator long() const; // can be marked explicit
};
Are there other solutions to prevent implicit type conversion for the assignment operator?
The assignment operator has a particular for Result& operator=(Result&);. In the assignment itself, there are no conversion. To prevent the implicit creation of a Result for the assignment, the constructor(s) need to be marked explicit.
To prevent the Result from being created from a ResultCode, you can either not declare the method, or mark it as deleted;
Result& operator=(ResultCode code) = delete;
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
}