Reputation: 97571
Is it possible to write a class such that these are valid:
Foo a;
Foo b = 0;
Foo c = b;
Foo d(0);
Foo e(1);
Foo f = Foo(1);
But these are not:
int x;
Foo a = x;
Foo b = 1;
Foo c = 2;
//etc
Essentially, my rule is "A constant 0
is implicitly convertible to a Foo
, but no other value is"
Upvotes: 8
Views: 150
Reputation: 14714
I admit I haven't achieved complete mastery of the rvalue semantics of C++11 yet, but this seems to do what you want:
class Foo
{
public:
Foo(int&&) {}
};
int main()
{
Foo a(123);
int x = 123;
Foo b(x); // error here, line 11
return 0;
}
Result:
prog.cpp:11: error: cannot bind ‘int’ lvalue to ‘int&&’
Comments welcome, if this code has any caveats I haven't noticed, or if you can reassure me that it doesn't.
Upvotes: -1
Reputation: 71919
If you don't mind Foo b = nullptr;
working, it's pretty easy to hack up. Have an explicit constructor from int
, and an implicit from std::nullptr_t
.
If you do mind that working, I'm not sure it's possible. The only way to distinguish between a literal 0
and other integer literals is the former's implicit conversion to pointers and nullptr_t
. So nullptr
will prefer a nullptr_t
parameter to a pointer parameter, so by having both constructors you could filter out nullptr
arguments. However, the conversions of 0
to pointers and nullptr_t
are of the same rank, so this would kill 0
arguments with an ambiguity.
Hmm ... something like this may work:
class Foo {
struct dummy;
public:
explicit Foo(int); // the version that allows Foo x(1);
Foo(dummy*); // the version that allows Foo x = 0;
template <typename T,
typename = typename std::enable_if<
std::is_same<T, std::nullptr_t>::value>::type>
Foo(T) = delete; // the version that prevents Foo x = nullptr;
};
I haven't actually tried this. In theory, the template should only participate in overload resolution when the argument is nullptr
, because otherwise SFINAE kills it. In that case, however, it should be better than the pointer constructor.
Upvotes: 5
Reputation: 24936
Foo e(1);
Will call a non-explicit constructor of Foo taking an int as argument. Essentially this line will do the same by trying to convert int to Foo using this int constructor.
Foo b = 1;
You can't prevent certain values of that int to be processed directly.
If you have your constructor explicit
you won't be able to write the next line, too.
Foo b = 0;
gx_ stated correctly that 0 can be converted to std::nullptr_t. The following will work with respect to your intent.
Foo(std::nullptr_t x) : member(0) { }
explicit Foo(int c) : member(c) { }
// ...
Foo a = 0; // compiles
Foo b = 1; // doesn't compile
// Note:
int do_stuff (void) { return 0; }
Foo c = do_stuff(); // doesn't compile
Upvotes: 0
Reputation: 97571
One idea I had was:
Foo(const uint32_t c) : member(0) { static_assert(c == 0, "Nope"); }
explicit Foo(uint32_t c) : member(c) { }
Does this behave sensibly?
Upvotes: -1