Explicit Type Conversion and Multiple Simple Type Specifiers

Explicit Type Conversion and Multiple Simple Type Specifiers

I posted this question to comp.lang.c++.moderated.

Daniel Krügler of the C++ standards committee agreed with the interpretation that unsigned int is a combination of simple type specifiers, and is not itself a simple type specifier.

Concerning the caption of table 7 referenced by Jerry Coffin, Krügler says:

I agree that the header of Table 7 (which is Table 9 in the most
recent draft N3000) is somewhat misleading, but the preceeding
text in [dcl.type.simple]/2 looks very clear to me, when it says:

Table 7 summarizes the valid combinations of simple-type-specifiers
and the types they specify."

(I apologize it took me so long to post this back here from the newsgroup; it completely slipped my mind)

Explicit type conversion (functional notation) with simple-type-specifier

GCC and CLANG are correct, the code is not valid.

Simple type specifier is single-word type name:

The simple type specifiers are

simple-type-specifier:
nested-name-specifieropt type-name
nested-name-specifier template simple-template-id
nested-name-specifieropt template-name
char
char16_t
char32_t
wchar_t
bool
short
int
long
signed
unsigned
float
double
void
auto
decltype-specifier

type-name:
class-name
enum-name
typedef-name
simple-template-id

decltype-specifier:
decltype ( expression )
decltype ( auto )

unsigned char is not a simple-type-specifier, it's a combination of simple-type-specifiers, as shown in Table 9 from standard.

Table [tab:simple.type.specifiers] summarizes the valid combinations of simple-type-specifiers and the types they specify.

Table 9 — simple-type-specifiers and the types they specify

Specifier(s)  Type
...
unsigned char “unsigned char”
...

Here's an explanation from cppreference.com:

2) The functional cast expression consists of a simple type specifier or a typedef specifier (in other words, a single-word type name: unsigned int(expression) or int*(expression) are not valid), followed by a single expression in parentheses.

What does the explicit keyword mean?

The compiler is allowed to make one implicit conversion to resolve the parameters to a function. What this means is that the compiler can use constructors callable with a single parameter to convert from one type to another in order to get the right type for a parameter.

Here's an example class with a constructor that can be used for implicit conversions:

class Foo
{
private:
int m_foo;

public:
// single parameter constructor, can be used as an implicit conversion
Foo (int foo) : m_foo (foo) {}

int GetFoo () { return m_foo; }
};

Here's a simple function that takes a Foo object:

void DoBar (Foo foo)
{
int i = foo.GetFoo ();
}

and here's where the DoBar function is called:

int main ()
{
DoBar (42);
}

The argument is not a Foo object, but an int. However, there exists a constructor for Foo that takes an int so this constructor can be used to convert the parameter to the correct type.

The compiler is allowed to do this once for each parameter.

Prefixing the explicit keyword to the constructor prevents the compiler from using that constructor for implicit conversions. Adding it to the above class will create a compiler error at the function call DoBar (42). It is now necessary to call for conversion explicitly with DoBar (Foo (42))

The reason you might want to do this is to avoid accidental construction that can hide bugs.

Contrived example:

  • You have a MyString class with a constructor that constructs a string of the given size. You have a function print(const MyString&) (as well as an overload print (char *string)), and you call print(3) (when you actually intended to call print("3")). You expect it to print "3", but it prints an empty string of length 3 instead.

C++11 standard ref for allowed type definitions in type specifier uses?

The reason it can't be found in the C++ standard is because it's actually prohibited in a delta from the C standard.

In C.1.4 we have the following: Change: Types must be declared in declarations, not in expressions In C, a sizeof expression or cast expression may create a new type. which shows the prohibition in question.

This is explicitly called out in 7.1.6/3:

At least one type-specifier that is not a cv-qualifier is required in
a declaration unless it declares a constructor, destructor or
conversion function.92 A type-specifier-seq shall not define a class
or enumeration unless it appears in the type-id of an
alias-declaration (7.1.3) that is not the declaration of a
template-declaration.

where the part of particular interest is that A type-specifier-seq shall not define a class or enumeration unless...

Fundamental types

Your logic error in your other answer was already explained by others. To let me explain to you again, given this statement:

The simple-type-specifiers specify either a previously-declared user-defined type or one of the fundamental types.

It does not mean that there are simple-type-specifiers for all fundamental types. It just means that each simple-type-specifier (or a combination of those - i think this sentence is not very clear) specify either a user defined type or one of the fundamental types. That statement also would apply to the following "sample-specifiers":

sample-specifier:
int
class-name

Each of my sample-specifiers specify either a previously declared user defined type, or one of the fundamental types (in my case, it's int). It does not mean that all previously declared user defined types can be denoted, nor does it mean that all fundamental types can be denoted. Now if another paragraph in the Standard says that type() works for type being a simple-type-specifier, that does not mean that it also must work for a combination of these. That's a totally invalid conclusion to do.

It's like when i say "decimal digits specify exclusively numbers from 0 to 9" and you say "you are wrong because they can't specify number 10". But what you did was to take a combination of two digits and then claim something i've never said. I think this is a pretty clear logical fallacy you do.

When to use explicit specifier for multi-argument constructors?

If your constructor is explicit and the class doesn't supply a non-explicit constructor taking initializer_list<T>, then you cannot copy-list-initialize an instance.

W w = {1,2}; // compiles without explicit, but not with

Simple live example

#include <iostream>

class A
{
public:
explicit A(int, int) {}
};

class B
{
public:
B(int, int) {}
};

int main()
{
B b = {1,2};
A a = {1,2};
}

Quotes from standard:

8.5/16

— If the initializer is a (non-parenthesized) braced-init-list, the
object or reference is list-initialized (8.5.4).

8.5.4/3

List-initialization of an object or reference of type T is defined as
follows: ...

Otherwise, if T is a class type, constructors are considered. The
applicable constructors are enumerated and the best one is chosen
through overload resolution (13.3, 13.3.1.7). If a narrowing
conversion (see below) is required to convert any of the arguments,
the program is ill-formed.

`auto` variable declaration with multi-word fundamental types causes error

For

auto foo = T{0};

to work, T has to be a simple-type-specifier.

From the C++11 Standard/5.2.3 Explicit type conversion (functional notation)/3

Similarly, a simple-type-specifier or typename-specifier followed by a braced-init-list creates a temporary object of the specified type direct-list-initialized ([dcl.init.list]) with the specified braced-init-list, and its value is that temporary object as a prvalue.

If you see the definition of simple-type-specifier, unsigned int is not one of them.

You can use any of the following:

auto foo = 0U;
auto foo = (unsigned int)0;
auto foo = static_cast<unsigned int>(0);

Implicit conversion in the other direction

You can just add a constructor that (1)accepts a single __m128 argument. If you don't add explicit then it provides an implicit conversion. This is called a converting constructor.

C++03 12.3.1/1

A constructor declared without the function-specifier explicit that can be called with a single parameter specifies a conversion from the type of its first parameter to the type of its class. Such a constructor is called a converting constructor.

C++11 (quite unreasonably, IMHO) extended the meaning of the term to cover “conversion” from multiple specified arguments, to the type of the constructor's class:

C++11 12.3.1/1

A constructor declared without the function-specifier explicit specifies a conversion from the types of its
parameters to the type of its class. Such a constructor is called a converting constructor.



1) If it has more arguments then these must be defaulted or be a ... ellipsis.

Explicit cast explanation in terms of memory for reference type in C#

For most cases, there's really not much conceivable difference between a reference variable and a pointer variable. Both point to a location in memory. The type of the reference (or pointer) variable tells the compiller which operations can be performed using it.

Instead of C pointers, which are (primarily) used with basic types (such as int or byte), consider C++ object pointers first. It's really almost the same as in C#:

MyBaseClass* a = new MyBaseclass();
a->BaseMethod(); // Call method using -> operator (dereference and call)

MyBaseClass* b = new MyDerivedClass();
b->DerivedMethod(); // Error: MyBaseClass has no such method

// Proper C++-Style casting.
MyDerivedClass* c = dynamic_cast<MyDerivedClass*>(b);
// Shortcut to the above, does not do the type test.
// MyDerivedClass* c = (MyDerivedClass*)b;
c->DerivedMethod(); // Ok

This translates almost 1:1 to C#, so reference types are (from a programmer point of view) just pointers with a defined type. The only visible difference would be that a direct C-Style cast in C# is equivalent to a try_cast in C++, which will ensure that you can never assign a wrong target instance to a reference variable.

So the differences between a reference type and a pointer to an object are (most of these are implied by the fact that C# is a managed language):

  • A reference variable can never point to invalid memory (except to NULL).
  • A reference variable can never point to an object that's not of its type.
  • When assigning a value to a reference variable, the type is always tested.
  • A cast on a reference variable needs to check that the target object is of the given type.

Is unsigned char('0') legal C++

No, it's not legal.

A function-style explicit type conversion requires a simple-type-specifier, followed by a parenthesized expression-list. (§5.2.3) unsigned char is not a simple-type-specifier; this is related to a question brought up by James.

Obviously, if unsigned char was a simple-type-specifier, it would be legal. A work-around is to use std::identity:

template <typename T>
struct identity
{
typedef T type;
};

And then:

int a = std::identity<unsigned char>::type('0');

std::identity<unsigned char>::type is a simple-type-specifier, and its type is simply the type of the template parameter.

Of course, you get a two-for-one with static_cast. This is the preferred casting method anyway.



Related Topics



Leave a reply



Submit