Question

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"

Was it helpful?

Solution

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.

OTHER TIPS

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

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?

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top