Search code examples
c++implicit-conversionexplicituniform-initialization

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?


Solution

  • 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.