Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent implicit conversion but allow list initialisation?

Let's say I have a class FunctionWrapper defined like this:

struct FunctionWrapper
{
  FunctionWrapper(std::function<void()> f);

  // ... plus other members irrelevant to the question
};

I'd like to prevent implicit conversions from std::function<void()> to FunctionWrapper, but allow constructing a FunctionWrapper using a brace initialisation syntax (that is, using list initialisation with a single argument). In other words, I'd like this:

void foo();
void wrap(FunctionWrapper);

wrap(foo); // (1) error
wrap({foo}); // (2) OK
wrap(FunctionWrapper{foo}); // (3) OK

Is there a way to achieve that? The way I've defined the class above is not it: this allows implicit conversions, so (1) compiles.

If I add explicit to the constructor:

struct FunctionWrapper
{
  explicit FunctionWrapper(std::function<void()> f);

  // ... plus other members irrelevant to the question
};

it doesn't help either, as that goes "too far" and disallows (2) as well as (1).

Is there a way to achieve "middle ground" and have (2) compile while (1) produces an error?

like image 454
Angew is no longer proud of SO Avatar asked Nov 21 '16 14:11

Angew is no longer proud of SO


1 Answers

Is there a way to achieve that?

Yes. You already have it.

wrap(foo);

For this to work, it would involve two user-defined conversions: void(*)() --> std::function<void()> --> FunctionWrapper, but we're only allowed up to one user-defined conversion. So this is an error and will be unless you add a separate constructor to FunctionWrapper to allow for it.

wrap({foo}); 

This is already fine, we're copy-list-initializing FunctionWrapper so the above limitation doesn't apply.

wrap(FunctionWrapper{foo});

This is clearly fine.


Note that this also provides a path forward for those cases where your first example actually worked. Let's say you had:

struct Wrapper {
    Wrapper(int ) { }
};

foo(0);           // want this to fail
foo({0});         // want this to be OK
foo(Wrapper{0});  // ... and this

You can't make the constructor explicit, since that causes foo({0}) to fail as well. But you can simply add another layer of indirection with another wrapper:

struct AnotherWrapper {
    AnotherWrapper(int i): i{i} { }
    int i;
};

struct Wrapper {
    Wrapper(AnotherWrapper ) { }
};

Here, wrap(0) fails, but wrap({0}) and wrap(Wrapper{0}) are both OK.

like image 139
Barry Avatar answered Oct 22 '22 21:10

Barry