Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Using explicit constructors for C++0x "return { expr };"

23 views
Skip to first unread message

Johannes Schaub (litb)

unread,
Sep 26, 2010, 10:47:16 PM9/26/10
to
I'm wondering about this one:

struct ALongAndComplexTypeName {
explicit A(int);
};

/* Possibly intermixed with enable_if huh */
ALongAndComplexTypeName f() {
return {5};
}

I would like to do something like that to avoid having to repeat the return
type name. Since return value copying forbids using explicit copy
constructors, the above will fail. Is there any work around?

Alf P. Steinbach /Usenet

unread,
Sep 27, 2010, 12:39:17 AM9/27/10
to
* Johannes Schaub (litb), on 27.09.2010 04:47:

<code>
namespace detail {
struct A // ALongAndComplexTypename
{
//A( A const& );
//A& operator=( A const& );

explicit A( int ) {}
};

A f() { return A( 5 ); }
}

typedef detail::A ALongAndComplexTypename;
using detail::f;

int main()
{
f();
}
</code>

But if you're not willing to put up with ALongAndComplexTypename in your own
implementation code, why are you foisting that name on client code?

Apart from that,


Cheers, & enjoy!,

- Alf

--
blog at <url: http://alfps.wordpress.com>

Luc Danton

unread,
Sep 27, 2010, 1:28:13 AM9/27/10
to

I suspect what was meant by a long typename is something like

typename std::conditional<
some_check<some_template_param>::value,
something,
something_else
>::type

where something & something else could both be constructed from the same
expression but are not convertible from one to the other.

In that (possibly contrived) case, you'd need to repeat yourself in the
body:

{
/* whatever */
typedef typename std::conditional<
some_check<some_template_param>::value,
something,
something_else
>::type ret_t;
return ret_t { some_expression };
}

Alf P. Steinbach /Usenet

unread,
Sep 27, 2010, 2:38:03 AM9/27/10
to
* Luc Danton, on 27.09.2010 07:28:

Uh, I was going to ask why you all are using needless C++0x syntax, but then I
discovered the thread's subject line.

Just a hint: when using MoronBird 3.x[1] you can avoid some of the quoting bugs
by selecting all the text before hitting "reply".

Anyways, I recall vaguely that in C++0x one can write something after the
function head? And perhaps there's a result_of or something in the standard lib?
Too busy to check right now, but worth checking I think! :-)


Cheers,

- Alf

Notes:
[1] In versions 1.x known as ThunderBird.

Luc Danton

unread,
Sep 27, 2010, 9:08:28 AM9/27/10
to

There is the delayed return type syntax yes.

int func();
can be written
auto func() -> int;

But its purpose is to have the parameters in scope (or declared? Not
sure about the proper terminology):

template<typename T>
auto func(T t)
-> decltype(*t);

You still can't factor out a type via a typedef (or using decl.) before
getting in the function body proper, where it's too late. std::result_of
is of no help either (I think).

However I've seen Boost.MPL use a "result_of" namespace, which doubles
up not only as a helper for convenient typedefs, but is actually used
for meta-computations. I think that's the proper way to "hide" those
computations from the user (if that's needed) and make maintenance
easier (but YMMV), and I use it too. So my own example would look like:

namespace result_of {

template</* whatever */>
struct f {
typedef typename std::conditional<
some_value,
something,
something
>::type type;
};

} // result_of

// Back in enclosing namespace
template</* whatever */>
typename result_of::f< ... >::type
f( ... )
{
...
typedef typename result_of::f< ... >::type ret_t;
return ret_t { /* something */ };
}


Also thanks for the TB tip; although I never noticed anything wrong with
the quotes.

Johannes Schaub (litb)

unread,
Sep 27, 2010, 9:46:00 AM9/27/10
to
Luc Danton wrote:

One very ugly thing to do is to use default template arguments

template<typename T, typename R = decltype(*declval<T>())>
R func(T t) {
return { *t };
}

SG

unread,
Sep 27, 2010, 9:52:54 AM9/27/10
to

ALongAndComplexTypeName *has* an implicit copy constructor but only an
explicit constructor taking an int. I think you're right, the code
won't work. The upcoming standard refers to this kind of
initialization as a "copy-list-initialization" which I guess belongs
to the copy-initialization category. So the explicit A(int) is not
viable for this kind of initialization.

After thinking about it, it seems that with C++0x's list
initialization for non-aggregate classes the keyword 'export' has also
an effect on constructors that take more than one parameter (ignoring
defaut arguments) -- unlike before in C++03. I havn't realized this
until now. So, if you don't want class type T

class T {
public:
T(int, double, void*);
};

to be copy-list-initializable we can make this constructor explicit so
that

void foo() {
T x = {1729,3.14159,nullptr};
}

won't compile anymore. Interesting.

> Is there any work around?

I don't see any. This doesn't really strike me as a big annoyance,
though.

Cheers!
SG

Johannes Schaub (litb)

unread,
Sep 27, 2010, 10:09:15 AM9/27/10
to
SG wrote:

> On 27 Sep., 04:47, Johannes Schaub wrote:
>> I'm wondering about this one:
>>
>> struct ALongAndComplexTypeName {
>> explicit A(int);
>> };
>>
>> /* Possibly intermixed with enable_if huh */
>> ALongAndComplexTypeName f() {
>> return {5};
>> }
>>
>> I would like to do something like that to avoid having to repeat the
>> return type name. Since return value copying forbids using explicit copy
>> constructors, the above will fail.
>
> ALongAndComplexTypeName *has* an implicit copy constructor but only an
> explicit constructor taking an int. I think you're right, the code
> won't work. The upcoming standard refers to this kind of
> initialization as a "copy-list-initialization" which I guess belongs
> to the copy-initialization category. So the explicit A(int) is not
> viable for this kind of initialization.
>

The worst, in my opinion, is that the constructor *is* viable, but it is not
allowed to be used.

struct A {
explicit A(bool);
A(std::string);
};

// error! explicit constructor used
A a = { "hello folks" };

// fine! A(bool) not viable
A a = "hello folks";

I dunno why that is desirable but it appears to me that it will have bad
effects on overload resolution.

struct Vector {
explicit Vector(int len);
};

struct MyInt {
MyInt(int I);
};

void print(Vector v);
void print(MyInt i);

// fine, call MyInt version
print(42);

// oops, ambiguity
print({42});

> After thinking about it, it seems that with C++0x's list
> initialization for non-aggregate classes the keyword 'export' has also
> an effect on constructors that take more than one parameter (ignoring
> defaut arguments) -- unlike before in C++03. I havn't realized this
> until now. So, if you don't want class type T
>
> class T {
> public:
> T(int, double, void*);
> };
>
> to be copy-list-initializable we can make this constructor explicit so
> that
>
> void foo() {
> T x = {1729,3.14159,nullptr};
> }
>
> won't compile anymore. Interesting.
>

I agree that this is intriguing, I have not thought about that case before.
There was until recently a way to do the same for a zero-parameter default
constructor

struct T {
explicit T();
};

// ill-formed! constructor is explicit
T t = { };

This was changed in the FCD though, because now it disregards overload
resolution and just calls that constructor as part of value initialization.
It wasn't all that useful anyway, I think :)


SG

unread,
Sep 27, 2010, 10:38:20 AM9/27/10
to
On 27 Sep., 16:09, "Johannes Schaub (litb)" wrote:
>
> struct A {
>   explicit A(bool);
>   A(std::string);
> };
>
> // error! explicit constructor used
> A a = { "hello folks" };

To my surprize I got the following error using G++ 4.4.1:

otest.cpp: In function 'int main()':
otest.cpp:13: error: call of overloaded 'A(<brace-enclosed
initializer list>)' is ambiguous
otest.cpp:8: note: candidates are: A::A(std::string)
otest.cpp:6: note: A::A(const A&)

I actually expected it to work and use A::A(string).
Hmm...

> // fine! A(bool) not viable
> A a = "hello folks";

This doesn't compile either using G++ 4.4.1:

otest.cpp:13: error: conversion from 'const char [12]' to
non-scalar type 'A' requested

I also expected this to work and use A::A(string).

> I dunno why that is desirable but it appears to me that it will have bad
> effects on overload resolution.
>
> struct Vector {
>   explicit Vector(int len);
> };
>
> struct MyInt {
>   MyInt(int I);
> };
>
> void print(Vector v);
> void print(MyInt i);
>
> // fine, call MyInt version
> print(42);

Ok, makes sense.

> // oops, ambiguity
> print({42});

Why is this supposed to be an ambiguity?

> SG wrote:
> > After thinking about it, it seems that with C++0x's list
> > initialization for non-aggregate classes the keyword 'export' has also
> > an effect on constructors that take more than one parameter (ignoring
> > defaut arguments) -- unlike before in C++03. I havn't realized this
> > until now. So, if you don't want class type T

Of course, I meant to write "explicit", not "export". :-)

> >   class T {
> >   public:
> >     T(int, double, void*);
> >   };
>
> > to be copy-list-initializable we can make this constructor explicit so
> > that
>
> >   void foo() {
> >     T x = {1729,3.14159,nullptr};
> >   }
>
> > won't compile anymore. Interesting.
>
> I agree that this is intriguing, I have not thought about that case before.
> There was until recently a way to do the same for a zero-parameter default
> constructor
>
> struct T {
>  explicit T();
> };
>
> // ill-formed! constructor is explicit
> T t = { };

Why would you even write this? T t{}; should do the trick.

> This was changed in the FCD though, because now it disregards overload
> resolution and just calls that constructor as part of value initialization.
> It wasn't all that useful anyway, I think :)

I currently don't claim to understand initialization. It sounds like
an awful lot of special cases to me. And *I* thought, it's going to
get more intuitive...

Cheers!
SG

Johannes Schaub (litb)

unread,
Sep 27, 2010, 11:21:33 AM9/27/10
to
SG wrote:

> On 27 Sep., 16:09, "Johannes Schaub (litb)" wrote:
>>
>> struct A {
>> explicit A(bool);
>> A(std::string);
>> };
>>
>> // error! explicit constructor used
>> A a = { "hello folks" };
>
> To my surprize I got the following error using G++ 4.4.1:
>
> otest.cpp: In function 'int main()':
> otest.cpp:13: error: call of overloaded 'A(<brace-enclosed
> initializer list>)' is ambiguous
> otest.cpp:8: note: candidates are: A::A(std::string)
> otest.cpp:6: note: A::A(const A&)
>
> I actually expected it to work and use A::A(string).
> Hmm...
>

Yes, I think this should use A::A(string). Copy-list-initialization has no
restriction onto nested user defined conversions as opposed to plain copy
initialization. I think the error here is that GCC4.4 does disregard
13.3.3.1/4 which says "by 13.3.1.7 (when passing the initializer list as a
single argument or when the initializer list has exactly one element) and (a
conversion to some class X or reference to (possibly cv-qualified) X is
considered for the first parameter of a constructor of X)" (parens by me to
hilight the binding of "and" and "or" in the wording, which I find easily
confused because "and" usually binds tighter :)

This is intended to factor out the copy constructor for list initialization
because since we are allowed to use nested user defined conversions, we
could always produce an ambiguous second conversion path by first invoking
the copy constructor and then doing the same as we did for the other
conversions.

>> // fine! A(bool) not viable
>> A a = "hello folks";
>
> This doesn't compile either using G++ 4.4.1:
>
> otest.cpp:13: error: conversion from 'const char [12]' to
> non-scalar type 'A' requested
>
> I also expected this to work and use A::A(string).
>

Oops, this is my fault. Yes GCC is fine not accepting this. Implicit
conversion sequence from "char const[12]" to A by A's constructor cannot use
a user defined conversion sequence to fit to the constructor parameter type
for plain copy initialization.

But my basic point was: Having explicit constructors still be considered
seems weird to me.

>> I dunno why that is desirable but it appears to me that it will have bad
>> effects on overload resolution.
>>
>> struct Vector {
>> explicit Vector(int len);
>> };
>>
>> struct MyInt {
>> MyInt(int I);
>> };
>>
>> void print(Vector v);
>> void print(MyInt i);
>>
>> // fine, call MyInt version
>> print(42);
>
> Ok, makes sense.
>
>> // oops, ambiguity
>> print({42});
>
> Why is this supposed to be an ambiguity?
>

Because of 13.3.1.7

"For copy-list-initialization, the candidate functions are all the
constructors of T. However, if an explicit constructor is chosen, the
initialization is ill-formed.".

There therefor exist an implicit conversion sequence of "{42}" to "Vector"
like there exist an implicit conversion sequence of a bit-field to a non-
const reference. In both cases, actually doing the initialization of the
parameter will be ill-formed, but that doesn't make the parameter non-
viable.

>> SG wrote:
>> > After thinking about it, it seems that with C++0x's list
>> > initialization for non-aggregate classes the keyword 'export' has also
>> > an effect on constructors that take more than one parameter (ignoring
>> > defaut arguments) -- unlike before in C++03. I havn't realized this
>> > until now. So, if you don't want class type T
>
> Of course, I meant to write "explicit", not "export". :-)
>
>> > class T {
>> > public:
>> > T(int, double, void*);
>> > };
>>
>> > to be copy-list-initializable we can make this constructor explicit so
>> > that
>>
>> > void foo() {
>> > T x = {1729,3.14159,nullptr};
>> > }
>>
>> > won't compile anymore. Interesting.
>>
>> I agree that this is intriguing, I have not thought about that case
>> before. There was until recently a way to do the same for a
>> zero-parameter default constructor
>>
>> struct T {
>> explicit T();
>> };
>>
>> // ill-formed! constructor is explicit
>> T t = { };
>
> Why would you even write this? T t{}; should do the trick.
>

I was just doing this out of interest. No real purpose behind this. Actually
I came to that idea when trying to make

T t;
t = {};

ill-formed. But I don't think there is any real-world purpose in making it
ill-formed :)

>> This was changed in the FCD though, because now it disregards overload
>> resolution and just calls that constructor as part of value
>> initialization. It wasn't all that useful anyway, I think :)
>
> I currently don't claim to understand initialization. It sounds like
> an awful lot of special cases to me. And *I* thought, it's going to
> get more intuitive...
>

I whole-heartly agree to you on this.

Johannes Schaub (litb)

unread,
Sep 27, 2010, 11:30:44 AM9/27/10
to
Johannes Schaub (litb) wrote:

Sorry I meant that it should error out because it uses the explicit
constructor (standard conversion sequence is better than user defined
conversion sequence). But that GCC4.4's ambiguity error message is out of
order: It should not consider the copy constructor.


Francesco S. Carta

unread,
Oct 1, 2010, 5:46:03 PM10/1/10
to
Alf P. Steinbach /Usenet <alf.p.stein...@gmail.com>, on
27/09/2010 08:38:03, wrote:

> Just a hint: when using [Thunder]Bird 3.x[1] you can avoid some of the


> quoting bugs by selecting all the text before hitting "reply".

Neat... from which cylinder did you pull that out?
Stomped on it by casualty?

Thanks for sharing!
[and please glide on the edit]

--
Francesco S. Carta
http://fscode.altervista.org

0 new messages