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

  • Thread starter Johannes Schaub (litb)
  • Start date
J

Johannes Schaub (litb)

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?
 
A

Alf P. Steinbach /Usenet

* Johannes Schaub (litb), on 27.09.2010 04:47:
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?

<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
 
L

Luc Danton

* 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

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

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

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 };
}
 
A

Alf P. Steinbach /Usenet

* Luc Danton, on 27.09.2010 07:28:
I suspect what was meant by a long typename is something like

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

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
return ret_t { some_expression };
}

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

Luc Danton

* Luc Danton, on 27.09.2010 07:28:
I suspect what was meant by a long typename is something like

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

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
return ret_t { some_expression };
}

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! :)

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

Johannes Schaub (litb)

Luc said:
* Luc Danton, on 27.09.2010 07:28:
On 27/09/2010 06:39, Alf P. Steinbach /Usenet wrote:
* Johannes Schaub (litb), on 27.09.2010 04:47:
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?

<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


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 };
}

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!
:)

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

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 };
}
 
S

SG

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.

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
 
J

Johannes Schaub (litb)

SG said:
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 :)
 
S

SG

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?

Of course, I meant to write "explicit", not "export". :)
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
 
J

Johannes Schaub (litb)

Johannes said:
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-qualiï¬ed) X is
considered for the ï¬rst 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.

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

Francesco S. Carta

on said:
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]
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,576
Members
45,054
Latest member
LucyCarper

Latest Threads

Top