Is there any ways to accept implicit conversion from one element initializer list but not a single value?

Viewed 59

The question itself may be confusing, so I'll describe it in detail here. Suppose we have a type S that represents a 1-dimensional vector (the linear algebra one, not the std one). Since it's kind of an array, so it's good if it behaves like std::array<int, 1>. Obviously, we don't want to allow construction with just a scalar, but implicit list initialization from a single element should be allowed. Shown in the following code snippet are the few initialization/conversion scenarios, I would prefer #2 and #4 to work while rejecting #1 and #3.

struct S
{
    explicit(?) S(int value);
};

S f1() { return 0; }      // #1
S f2() { return { 0 }; }  // #2

void g()
{
    S s1 = 0;             // #3
    S s2{ 0 };            // #4
}

Making the constructor explicit would disallow #1 and #3, which is nice, but as a side effect, it also disallows #2. #2 should be allowed in this scenario, just like how std::array<int, 1> works.

Since the type would have other constructors defined, and I would like the data to be private, so AFAIK it's not possible to use the same technique as std::array (aggregate init + brace elision). Are there any means to achieve this goal? I don't mind if some bizarre tricks must be used.

Thanks!


Edit:

  • I have tried explicit(false) S(std::array<int, 1>); and it works, but it needs an additional pair of braces for cases #2 and #4. It would be great if the same goal can be achieved without the extra braces.
1 Answers

This isn't possible. Since S is not an aggregate and the initializer-list is not empty, you go straight to considering constructors for return {0};. That's the same overload resolution as for the (non-list) copy-initialization for return 0;, except that explicit constructors are disallowed rather than disregarded.

Oddly enough, this means you can have the inverse behavior: by providing an implicit and an explicit constructor that are ambiguous for construction from int, you can make return {0}; fail due to ambiguity while having return 0; select the converting constructor.

One might try a trick involving an intermediate type:

struct hold {hold(int);};
struct S {
  S(hold);
};

Unfortunately, the rules for multiple user-defined conversions are also consistent between copy- and copy-list-initialization, so the S(hold(0)) interpretation won't be tried for either mode of construction. (There are cases where multiple levels of braces allow multiple user-defined conversions, but that's not relevant here by hypothesis.)

Related