9

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?

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • 4
    Add an explicit constructor that takes a `std::initializer_list` parameter. – Sam Varshavchik Nov 21 '16 at 14:31
  • 3
    you could use initializer_list but then you would have only runtime check for number of arguments... maybe template accepting const array and static_assert on template size... but that just ugly – Hcorg Nov 21 '16 at 14:32
  • @Hcorg wouldn't [this](http://melpon.org/wandbox/permlink/0rFgVYD8XKYwVlC1) require double brace initialization? – W.F. Nov 21 '16 at 15:05
  • @W.F. true... too make it work `wrap` should be overloaded :/ – Hcorg Nov 21 '16 at 15:16
  • 2
    In your first example, [only the first won't fail to compile already,](http://coliru.stacked-crooked.com/a/074c42f0137e3d6c) because the implicit conversion required from function pointer to `function<>` consumes the only allowed implicit conversion for the argument. I assume you want this to be the case even when the supplied argument is already a `function<>`? – jaggedSpire Nov 21 '16 at 15:19
  • 2
    `wrap(foo)` is already an error, and will always be an error. `wrap({foo})` already compiles. I don't understand your question. – Barry Nov 21 '16 at 15:29

1 Answers1

8

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.

Barry
  • 286,269
  • 29
  • 621
  • 977