type promotion for some constructors but not others

Z

Zak

I have some c++ source where construction via

Type var(init); // compiles and...

Type var = init; // does not

I thought the two are supposed to be completely equivalent? Why does
type promotion seem to take place on the first line, but not the
second? Is there a way it can be enabled for the second?

In this case, init is an enum constant contained within Type. The
compiler is g++ 4.x. Both lines work with VS8, unless you turn
language extensions off, in which case you get the g++ behavior.
(error: conversion from ‘TestChc::Choices’ to non-scalar type ‘Test’
requested). Here's the complete code:

#include <iostream>
#include <string>
using namespace std;

#define TWOCHARINT(A, B) (A+(B<<8)) // FIXME: wrong for big endian
archs

template<typename EnumStrucT> struct EnumChoices: EnumStrucT
{EnumChoices(): EnumStrucT() {} // only works if EnumStrucT
default constructs
EnumChoices(const EnumStrucT& arg): EnumStrucT(arg) {}
EnumChoices& operator=(const EnumStrucT& arg)
{this->value = arg.value; return *this;} // ^ EnumStrucT handles
derivations

bool operator== (const EnumStrucT& arg) const
{return this->value.asScalar==arg.value.asScalar;}

bool operator!= (const EnumStrucT& arg) const
{return this->value.asScalar!=arg.value.asScalar;}

const string asString() const
{return string(EnumStrucT::value.asChars, sizeof
EnumStrucT::value.asChars);}

const uint16_t asScalar() const {return EnumStrucT::value;}};

struct TestChc
{enum Choices {CN = 0, C1 = '1', CB = TWOCHARINT('A', 'B'), CS = '
', CZ = 'Z'};
union Value
{uint16_t asScalar;
char asChars[2];
Value(const Choices& arg): asScalar(arg) {}} value;
TestChc(const Choices& arg):value(arg) {}};

typedef EnumChoices<TestChc> Test;

Test& func() {return *(new Test(TestChc::CZ));}

int main (int, char**)
{Test t(TestChc::CB); // , tf('X'); is invalid
// Test t2 = TestChc::CB; // <- this works in VS
Test t2 = func();
// Test t2; // this only works if TestChc default constructs
// Test t2(TestChc::CS);
cout << "size is " << sizeof t2 << endl;
cout << t.asString() << '\t' << t2.asString() << endl;
t = t2;
cout << (t==t2) << endl;
t = TestChc::C1;
cout << (t!=t2) << endl;
return 0;}

There's a little more than needed there, but the crux of the question
is why does the first declaration in main compile while the second (if
uncommented) does not?

Thanks in advance.
Zachary
 
A

acehreli

I have some c++ source where construction via

    Type var(init);  // compiles and...

    Type var = init;  // does not

Because the compiler considers only one user defined conversion.
    template<typename EnumStrucT> struct EnumChoices: EnumStrucT
    {EnumChoices(): EnumStrucT() {} // only works if EnumStrucT
default constructs
     EnumChoices(const EnumStrucT& arg): EnumStrucT(arg) {}
     EnumChoices& operator=(const EnumStrucT& arg)
     {this->value = arg.value; return *this;} // ^ EnumStrucT handles
derivations
    struct TestChc
    {enum Choices {CN = 0, C1 = '1', CB = TWOCHARINT('A', 'B'), CS = '
', CZ = 'Z'};
     union Value
     {uint16_t asScalar;
      char asChars[2];
      Value(const Choices& arg): asScalar(arg) {}} value;
     TestChc(const Choices& arg):value(arg) {}};
    // Test t2 = TestChc::CB; // <- this works in VS

Two conversions needed, which the compiler will not consider:

TestChc::Choices -> TestChc
TestChc -> Test

Ali

P.S. Your code needs lots of vertical white space to be readable.
 
A

Andrey Tarasevich

Zak said:
I have some c++ source where construction via

Type var(init); // compiles and...

Type var = init; // does not

I thought the two are supposed to be completely equivalent?

They are not. They are equivalent only in one particular case when the
source and destination types are the same. If the types are different
(as in your code), these forms are not equivalent.
Why does
type promotion seem to take place on the first line, but not the
second?

Because that how these initializations are supposed work in C++.

The first one

Test t(TestChc::CB);

selects the appropriate constructor through ordinary overload
resolution. In your example there are two candidates (I substituted the
template parameters)

Test::Test(const Test& arg); // <- implicitly declared
Test::Test(const TestChc& arg);

Since the actual argument type is 'TestChc::Choices', the first
candidate cannot be selected, because there's no valid implicit
conversion from 'TestChc::Choices' argument to the 'Test' parameter
type. The second candidate can be called with an argument of
'TestChc::Choices' type after performing an implicit user-defined
conversion to 'TestChc' type (using conversion constructor in
'TestChc'). Which is why the second constructor is selected. This is
ordinary overload resolution, nothing unusual about it.

The second form

Test t2 = TestChc::CB;

works in accordance with a different algorithm. It attempts to perform
an implicit conversion of the source value to the temporary object of
destination type and then copies the temporary using the copy
constructor. Basically, it is similar to the previous one, but the
compiler is _required_ to select the copy-constructor (the first
candidate in the above list). Your 'Test' class has a compiler-provided
copy constructor, so that part is OK. However, in order to call that
constructor we, once again, have to be able to perform an implicit
conversion from 'TestChc::Choices' to 'Test'. And you, again, provided
no way to perform such a conversion in your code within the C++ rules of
implicit conversions.
Is there a way it can be enabled for the second?

Well, there are many ways to enable it, but what makes sense and what
doesn't depends on the design. Provide a way to perform the conversion,
for one....
In this case, init is an enum constant contained within Type. The
compiler is g++ 4.x. Both lines work with VS8, unless you turn
language extensions off, in which case you get the g++ behavior.
(error: conversion from ‘TestChc::Choices’ to non-scalar type ‘Test’
requested). Here's the complete code:

#include <iostream>
#include <string>
using namespace std;

#define TWOCHARINT(A, B) (A+(B<<8)) // FIXME: wrong for big endian
archs

template<typename EnumStrucT> struct EnumChoices: EnumStrucT
{EnumChoices(): EnumStrucT() {} // only works if EnumStrucT
default constructs
EnumChoices(const EnumStrucT& arg): EnumStrucT(arg) {}
EnumChoices& operator=(const EnumStrucT& arg)
{this->value = arg.value; return *this;} // ^ EnumStrucT handles
derivations

bool operator== (const EnumStrucT& arg) const
{return this->value.asScalar==arg.value.asScalar;}

bool operator!= (const EnumStrucT& arg) const
{return this->value.asScalar!=arg.value.asScalar;}

const string asString() const
{return string(EnumStrucT::value.asChars, sizeof
EnumStrucT::value.asChars);}

const uint16_t asScalar() const {return EnumStrucT::value;}};

struct TestChc
{enum Choices {CN = 0, C1 = '1', CB = TWOCHARINT('A', 'B'), CS = '
', CZ = 'Z'};
union Value
{uint16_t asScalar;
char asChars[2];
Value(const Choices& arg): asScalar(arg) {}} value;
TestChc(const Choices& arg):value(arg) {}};

typedef EnumChoices<TestChc> Test;

Test& func() {return *(new Test(TestChc::CZ));}

int main (int, char**)
{Test t(TestChc::CB); // , tf('X'); is invalid
// Test t2 = TestChc::CB; // <- this works in VS
Test t2 = func();
// Test t2; // this only works if TestChc default constructs
// Test t2(TestChc::CS);
cout << "size is " << sizeof t2 << endl;
cout << t.asString() << '\t' << t2.asString() << endl;
t = t2;
cout << (t==t2) << endl;
t = TestChc::C1;
cout << (t!=t2) << endl;
return 0;}

There's a little more than needed there, but the crux of the question
is why does the first declaration in main compile while the second (if
uncommented) does not?
 
J

James Kanze

Even in that case, the second form can create a temporary if
init is not Type or something derived from it.

In theory, at least.
Type var = init;
can generate either of the following code-wise
Type var( (Type(init)) );
Type var(init);
but must still check for validity as if the first form were used.

Except that if init has type Type, then if the second is legal,
so is the first. In both cases, you're constructing a Type from
another type.
 

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,766
Messages
2,569,569
Members
45,042
Latest member
icassiem

Latest Threads

Top