Uniform initialization is too restrictive with explicit constructors

308 views
Skip to first unread message

stackm...@hotmail.com

unread,
Dec 15, 2012, 8:50:03 AM12/15/12
to std-pr...@isocpp.org
It has often occured to me that I have a function returning a std::unique_ptr. Naturally, I would expect this to work:
struct foo {};

std
::unique_ptr<foo> bar(bool baz)
{
   
if(baz)
       
return {};

   
return { new foo };
}
The problem is the foo* constructor here, because it is explicit (for very good reasons). What I have to write instead is the following:
return std::unique_ptr<foo>{ new foo };
It's not much better with a make_unique facility.

The same problem applies to std::tuple too.
void foo(std::tuple<int, double, std::string>);

foo({ 42, 3.14, "Hello, World" }); // error
foo(
std::tuple<int, double, std::string>{ 42, 3.14, "Hello, World" }); // works, but eww
foo(make_tuple(
42, 3.14, "Hello, World")); // works, but i didn't use it for type deduction, which is against its intent
I think the {}-syntax is already explicit enough, therefore it should allow calling explicit constructors without the type. Is there anything I have missed?

R. Martinho Fernandes

unread,
Dec 15, 2012, 9:02:55 AM12/15/12
to std-pr...@isocpp.org
I have once before asked about this on Stack Overflow http://stackoverflow.com/questions/9157041/what-could-go-wrong-if-copy-list-initialization-allowed-explicit-constructors. I would really like to get rid of this nuissance.

Daniel Krügler

unread,
Dec 15, 2012, 10:06:27 AM12/15/12
to std-pr...@isocpp.org
2012/12/15 R. Martinho Fernandes <martinho....@gmail.com>:
> I have once before asked about this on Stack Overflow
> http://stackoverflow.com/questions/9157041/what-could-go-wrong-if-copy-list-initialization-allowed-explicit-constructors.
> I would really like to get rid of this nuissance.

I'm not convinced yet that there is a defect here in regard to
explicit constructors. I don't think that the problem is that you have
to to provide some explicit naming, the problem is some form of naming
duplication, which also has the tendency to be missed by code
refactorings. A possible solution could be based on a language tool
that already exists for similar reasons, namely auto. What about:

std::unique_ptr<foo> bar(bool baz)
{
if(baz)
return {};

return auto{ new foo };
}

This makes clear for the second return statement, that

a) we don't return foo, but we return the type already named as
function return type, suitably converted *from* "new foo"
b) it get rids of the naming duplication of the return type (which
might be changed to some other smart pointer type, for example)

This satisfies similar needs as auto for object declarations and is
more or the less the counterpart to lambda expression with auto-return
type deduction.

- Daniel

John Bytheway

unread,
Dec 15, 2012, 10:10:50 AM12/15/12
to std-pr...@isocpp.org
On 15/12/12 08:50, stackm...@hotmail.com wrote:
> It has often occured to me that I have a function returning a
> std::unique_ptr. Naturally, I would expect this to work:|
> |
> ||
> |structfoo {};
>
> std::unique_ptr<foo>bar(boolbaz)
> {
> if(baz)
> return{};
>
> return{newfoo };
> }|
> The problem is the foo* constructor here, because it is explicit (for
> very good reasons). What I have to write instead is the following:
> ||
> ||returnstd::unique_ptr<foo>{newfoo };
> ||
> It's not much better with a make_unique facility.
>
> The same problem applies to std::tuple too.
> ||
> void foo(std::tuple<int, double, std::string>);
>
> foo({ 42, 3.14, "Hello, World" }); // error
> foo(|std::tuple<int, double, std::string>|{|42, 3.14, "Hello, World"
> |}); // works, but eww
> foo(make_tuple(|42, 3.14, "Hello, World"|)); // works, but i didn't use
> it for type deduction, which is against its intent

Concur. These two cases in particular (unique_ptr and tuple) have vexed
me on several occasions.

> I think the {}-syntax is already explicit enough, therefore it should
> allow calling explicit constructors without the type. Is there anything
> I have missed?

The most recent document I can find on this topic with rationale is
N2352
<http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2532.pdf>.
However, this appears to explicitly state that the above style of
construct should be valid. I guess something changed after that. Can
anyone point to a more recent document?

I think the only way to fix this without it being a breaking change is
to introduce another keyword to allow explicit constructor to opt back
in to copy-list-initialization. e.g.

explicit listinit unique_ptr(pointer p) noexcept;

This is moderately ugly, but I could live with it.

(Note that this is only not a breaking change at the language level. If
we change standard library constructors (which of course we must to
avoid the above issues) then we risk breaking code)

However, my preference would be to simply scrap the clause and allow all
constructors in copy-list-initialization. I have not yet seen a
compelling argument against this route.

In either case, I think that there would be very few breakages.

John Bytheway

Ville Voutilainen

unread,
Dec 15, 2012, 10:16:52 AM12/15/12
to std-pr...@isocpp.org
On 15 December 2012 17:10, John Bytheway <jbyt...@gmail.com> wrote:
> Concur. These two cases in particular (unique_ptr and tuple) have vexed
> me on several occasions.
>> I think the {}-syntax is already explicit enough, therefore it should
>> allow calling explicit constructors without the type. Is there anything
>> I have missed?
> The most recent document I can find on this topic with rationale is
> N2352
> <http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2532.pdf>.
> However, this appears to explicitly state that the above style of
> construct should be valid. I guess something changed after that. Can
> anyone point to a more recent document?

There was a discussion about this issue in the Portland, and there was
significant resistance to making a change. The proposal paper was late,
and didn't appear in any mailing, and the proposal author afaik is not
going to pursue the idea further, because it was rejected in the EWG.

Vicente J. Botet Escriba

unread,
Dec 15, 2012, 10:31:24 AM12/15/12
to std-pr...@isocpp.org
Le 15/12/12 14:50, stackm...@hotmail.com a écrit :
It depends on what do you consider enough explicit. The language request the type when a explicit constructor is required. {}-syntax builds a initializer-list that can be used as parameter of a function/constructor but not which one. Of course in the return context the function return type could be the 'implicit' type used to call the explicit constructor.
IMO, the implicit type should be a little bit more explicit, e.g using auto or explicit

    return auto({ new foo });
or
    return explicit({ new foo });


An alternative could be to declare an implicit unique_ptr constructor from an initializer list.

Another alternative is to build a new type that is implicitly convertible to any type

    return implicit(new foo);

template <typename T>
class implicitly_convertible {
  T value;
public:
  implicitly_convertible(T val) : value(val) {}
  template <typename U>
  operator U() { return U(value); }
};

template <typename T>
implicitly_convertible<T> implicit(T value) { return implicitly_convertible<T>(value); }
 
Vicente

DeadMG

unread,
Dec 15, 2012, 3:39:23 PM12/15/12
to std-pr...@isocpp.org
I agree. When you have {} in an expression, it's pretty explicit that you are constructing a new object of some type, with the arguments inside the braces. I think that explicit constructors should be available for use. The possible impact on legacy code, though, I am uncertain.

Fernando Pelliccioni

unread,
Dec 16, 2012, 12:23:09 PM12/16/12
to std-pr...@isocpp.org
On Sat, Dec 15, 2012 at 5:39 PM, DeadMG <wolfei...@gmail.com> wrote:
I agree. When you have {} in an expression, it's pretty explicit that you are constructing a new object of some type, with the arguments inside the braces. I think that explicit constructors should be available for use. The possible impact on legacy code, though, I am uncertain.

--

Here another similar discussion I posted some time ago.


It had little impact at the forum...
I think the standard should be changed to support the examples presented.

I have no experience in making proposals, but if we create a team, I volunteer to work at it. 


Jeffrey Yasskin

unread,
Dec 16, 2012, 6:21:35 PM12/16/12
to std-pr...@isocpp.org
A language extension isn't essential here if you're willing to accept
an extra token in the return:

template<typename T>
struct Converter {
T val_;
Converter(T val) : val_(std::forward<T>(val)) {}
template<typename U> /*implicit*/ operator U() { return
U(std::forward<T>(t)); }
};
template<typename T>
Converter<T> convert(T&& val) { return Converter<T>(std::forward<T>(val)); }

std::unique_ptr<int> foo() {
return convert(new int(3));
}

See the (non-public, sorry) thread including c++std-ext-14049.

Jeffrey

Sebastian Gesemann

unread,
Dec 17, 2012, 12:04:45 PM12/17/12
to std-pr...@isocpp.org
On Sat, Dec 15, 2012 at 2:50 PM, stackmachine wrote:
> It has often occured to me that I have a function returning a
> std::unique_ptr. Naturally, I would expect this to work:
> struct foo {};
>
> std::unique_ptr<foo> bar(bool baz)
> {
> if(baz)
> return {};
> return { new foo };
> }
>
> The problem is the foo* constructor here, because it is explicit (for very
> good reasons). What I have to write instead is the following:
> return std::unique_ptr<foo>{ new foo };
> It's not much better with a make_unique facility.

I agree.

> The same problem applies to std::tuple too.
> void foo(std::tuple<int, double, std::string>);
>
> foo({ 42, 3.14, "Hello, World" }); // error

Wow. This is an error? I did not expect the corresponding tuple
constructor to be explicit. Why is it explicit in the first place?!

> I think the {}-syntax is already explicit enough, therefore it should allow
> calling explicit constructors without the type. Is there anything I have
> missed?

I'm not convinced that it's a good idea to allow _any_ kind of {}
initialization to also pick an explicit constructor. We have two
classes of initialization: direct-initialization (which has access to
explicit constructors) and copy-initialization (which has only access
to implicit constructors. This is also the case for list
initialization: direct-list-initialization, copy-list-initialization
and I think it should stay that way simply because you can exploit
explicit constructors to avoid accidental conversions during overload
resulution and such.

The return statement is said to copy-initialize the return value.
Maybe, it's a good idea to say that "returning a list" is considered a
direct-list-initialization. In this case (returning a list) I really
don't see the harm in treating this as direct-list-initialization.
It's not like we could pick the wrong overload. There is just one
target type and it's the function's declared return-type. I don't see
how any "accidental conversion" could happen here.

Ville Voutilainen

unread,
Dec 17, 2012, 12:19:05 PM12/17/12
to std-pr...@isocpp.org
On 17 December 2012 19:04, Sebastian Gesemann <s.ges...@gmail.com> wrote:
> The return statement is said to copy-initialize the return value.
> Maybe, it's a good idea to say that "returning a list" is considered a
> direct-list-initialization. In this case (returning a list) I really
> don't see the harm in treating this as direct-list-initialization.
> It's not like we could pick the wrong overload. There is just one
> target type and it's the function's declared return-type. I don't see
> how any "accidental conversion" could happen here.

All of these points, including the one-target-type were presented by
Herb in Portland,
aka the last meeting. Unfortunately he hasn't released his paper, it
would be good for
the sake of having a paper trail. Anyway, the EWG wasn't convinced by
these arguments.
My take on it is that

return {new Foo};

may be "explicit" enough, but what about

return {p};

where p is what-ever pointer, or a member that is a pointer? I don't
consider that explicit
enough, and I don't want to even think about making a special rule for
any new-expressions
or rvalues or anything like that. The tuple case has an open library issue, see
http://open-std.org/JTC1/SC22/WG21/docs/papers/2012/n3473.html#2051
(you may recognize the issue submitter, although it's from one of my
constituents, I just
reported it), but for unique_ptr I don't think we should go towards
the direction of making
ownership transfer any more implicit than it is, and the same applies
to shared_ptr as far
as I am concerned.

Olaf van der Spek

unread,
Dec 17, 2012, 1:56:23 PM12/17/12
to std-pr...@isocpp.org
On Saturday, December 15, 2012 2:50:03 PM UTC+1, stackm...@hotmail.com wrote:
The problem is the foo* constructor here, because it is explicit (for very good reasons). What I have to write instead is the following:
return std::unique_ptr<foo>{ new foo };
It's not much better with a make_unique facility.

How so?
return std::make_unique<foo> is simpler;

stackm...@hotmail.com

unread,
Dec 17, 2012, 2:35:15 PM12/17/12
to std-pr...@isocpp.org
It is just ugly, compared to { new foo }.

DeadMG

unread,
Dec 17, 2012, 3:42:04 PM12/17/12
to std-pr...@isocpp.org
I think that for passing arguments, it is not so bad, because then you have overload resolution and such. However, when you are returning, there is only one type possibly under consideration, and it's quite explicit that you are constructing that type. Having to specify the return type again is just unnecessary code duplication.

Olaf van der Spek

unread,
Dec 17, 2012, 6:56:52 PM12/17/12
to std-pr...@isocpp.org
That's the choice made when making a constructor explicit, isn't it?

Should those two be equivalent?
vector v(10);
vector v = 10;

Should return 10 work when the return type is vector? Should return {
10 }; work?

Are we looking for a middle ground between implcit and explicit for some cases?


--
Olaf

stackm...@hotmail.com

unread,
Dec 18, 2012, 11:15:44 AM12/18/12
to std-pr...@isocpp.org
Wouldn't return {10}; just call the initializer_list constructor?
Yes, I think we're looking for something inbetween implicit and explicit.

Olaf van der Spek

unread,
Dec 18, 2012, 11:17:00 AM12/18/12
to std-pr...@isocpp.org
On Tue, Dec 18, 2012 at 5:15 PM, <stackm...@hotmail.com> wrote:
> Wouldn't return {10}; just call the initializer_list constructor?

Yes, but suppose we've got a class that doesn't have such a constructor.



--
Olaf

stackm...@hotmail.com

unread,
Dec 18, 2012, 11:37:06 AM12/18/12
to std-pr...@isocpp.org
IMO, return { 10 }; should be allowed, return 10 shouldn't.

Arthur Tchaikovsky

unread,
Dec 18, 2012, 12:10:39 PM12/18/12
to std-pr...@isocpp.org
Hi,
you are incorrect when you state:
struct like_std_string
{
  template<class InputIterator>
  like_std_string(InputIterator begin, InputIterator end);      // This is a converting constructor.
};
 
The point is that "like_std_string(InputIterator begin, InputIterator end);" is not a converting constructor
Regards
Reply all
Reply to author
Forward
0 new messages