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

Type traits and accessibility

9 views
Skip to first unread message

Nikolay Ivchenkov

unread,
Apr 13, 2010, 12:41:11 PM4/13/10
to
According to N3092 - 11/5,
"The interpretation of a given construct is established without regard
to access control. If the interpretation established makes use of
inaccessible member names or base classes, the construct is ill-
formed".

Does it mean that the following program is ill-formed?

#include <type_traits>

class X
{
public:
X() {}
private:
X(int) {}
};

int main()
{
int bool value =
std::is_constructible<X, int>::value;
}

--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Daniel Krügler

unread,
Apr 13, 2010, 5:57:35 PM4/13/10
to
On 13 Apr., 18:41, Nikolay Ivchenkov <ts...@mail.ru> wrote:
> According to N3092 - 11/5,
> "The interpretation of a given construct is established without regard
> to access control. If the interpretation established makes use of
> inaccessible member names or base classes, the construct is ill-
> formed".

This is the general core language rule and applies to
everything which is does not say otherwise.

> Does it mean that the following program is ill-formed?
>
> #include <type_traits>
>
> class X
> {
> public:
> X() {}
> private:
> X(int) {}
> };
>
> int main()
> {
> int bool value =
> std::is_constructible<X, int>::value;
> }

This is well-formed, because std::is_constructible
needs compiler-support (same as is_convertible
does) to realize it's specification, see
[meta.unary.prop]/6:

"Given the following function prototype:

template <class T>
typename add_rvalue_reference<T>::type create();

the predicate condition for a template specialization
is_constructible<T, Args...> shall be satisfied if and
only if the following expression CE would be well-formed:

� if sizeof...(Args) == 1, the expression:
static_cast<T>(create<Args>()...)
� otherwise, the expression:
T(create<Args>()...)"

Note the usage of "would be well-formed". For your
example the effective test-expression is

static_cast<X>(create<int>())

This expression *would* be ill-formed, because
attempting to evaluate it inside main would
stumble across the lack of access here. The result
is, that the program is well-formed and that
"value" will evaluate to false.

It may well be that your current compiler declares
the program as ill-formed because it just simulates
the semantics via "normal" code. But this is not
a compliant implementation.

HTH & Greetings from Bremen,

Daniel Kr�gler

P.S.: Note that there is a open library issue in regard
to is_constructible, but it does not affect your example:

http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#1260

Nikolay Ivchenkov

unread,
Apr 13, 2010, 5:55:25 PM4/13/10
to
> #include <type_traits>
>
> class X
> {
> public:
> X() {}
> private:
> X(int) {}
> };
>
> int main()
> {
> int bool value =
> std::is_constructible<X, int>::value;
> }

Amendment:

#include <type_traits>

class X
{
public:
X() {}
private:
X(int) {}
};

int main()
{
const bool value =

Nikolay Ivchenkov

unread,
Apr 14, 2010, 3:26:58 AM4/14/10
to
On 14 Apr, 01:57, Daniel Kru"gler <daniel.krueg...@googlemail.com>
wrote:

>
> "Given the following function prototype:
>
> template <class T>
> typename add_rvalue_reference<T>::type create();
>
> the predicate condition for a template specialization
> is_constructible<T, Args...> shall be satisfied if and
> only if the following expression CE would be well-formed:
>
> if sizeof...(Args) == 1, the expression:
> static_cast<T>(create<Args>()...)
> otherwise, the expression:
> T(create<Args>()...)"

The correctness of such expressions depends on context where they
appear. It would be very surprising if the same member of the same
template specialization could have different compile-time values. A
library template cannot provide such behavior because immediate
context of its use is not available within the template definition:

namespace std {
template <class T, class... Args>
struct is_constructible :
integral_constant
<
bool,
__is_constructible(T, Args...)
// we don't know immediate context here
>
{
};
}

Only core language construct could check accessibility with regard to
immediate context. So, if the value of expression
"std::is_constructible<X, int>::value" depends on the constructor's
accessibility in a given context, then is_constructible is actually
not a part of the library, instead it shall be considered as core
language construct like dynamic_cast.

I need more clear explanation - what exactly affects the value of the
expression "std::is_constructible<X, int>::value": the constructor's
publicity only or its accessibility in a context where the member
"value" of the specialization is accessed?

Daniel Krügler

unread,
Apr 14, 2010, 7:19:03 AM4/14/10
to

My explanation was a bit misleading, but the normative
wording should be correct. Notice that the specification
describes a context that would require public access
in all cases.

> So, if the value of expression
> "std::is_constructible<X, int>::value" depends on the constructor's
> accessibility in a given context, then is_constructible is actually
> not a part of the library, instead it shall be considered as core
> language construct like dynamic_cast.

Well, this is a stylistic question, and does not have any
normative impact. If the library defines semantics of
components that does not work without compiler-support,
this is maybe odd but nothing fundamentally wrong.

During the short concept era this was more clearly
expressed (at least for the ingenuous reader of the
standard) via the (compiler) support concepts. But
when concepts where removed for C++0x, this
was all undone and we felt back into the middle
ages situation depending on library-defined type
traits. Again, this is not restricted to std::is_constructible.
Your question applies to other compiler support
type traits like std::is_convertible. Others are not
so special: E.g. std::common_type or std::result_of
don't provide this extended support.

> I need more clear explanation - what exactly affects the value of the
> expression "std::is_constructible<X, int>::value": the constructor's
> publicity only or its accessibility in a context where the member
> "value" of the specialization is accessed?

The constructor must have public access as explained above.
Notice that there is no need of a real constructor, the only
relevant thing is the initialization/conversion expression, so
std::is_constructible also applies to built-in types and references.

HTH & Greetings from Bremen,

Daniel Krügler

Mathias Gaunard

unread,
Apr 14, 2010, 7:18:22 AM4/14/10
to
On 13 avr, 22:57, Daniel Krügler <daniel.krueg...@googlemail.com>
wrote:
> std::is_constructible
> needs compiler-support

Not necessarily.
You could use SFINAE to check whether the expressions that defines
is_contructible's value are well-formed.

Daniel Krügler

unread,
Apr 14, 2010, 2:17:14 PM4/14/10
to
On 14 Apr., 13:18, Mathias Gaunard <loufo...@gmail.com> wrote:
> On 13 avr, 22:57, Daniel Krügler <daniel.krueg...@googlemail.com>
> wrote:
>
> > std::is_constructible
> > needs compiler-support
>
> Not necessarily.
> You could use SFINAE to check whether the expressions that defines
> is_contructible's value are well-formed.

Nope, SFINAE is *not* access-tolerant. Just try

template<class T>
struct HasStaticFoo {
typedef char No;
typedef char(&Yes)[2];
template<class U, class = decltype(U::foo())>
static Yes test(void*);
template<class>
static No test(...);
static const bool value = sizeof(test<T>(0)) == sizeof(Yes);
};

struct S {
static void foo();
};

struct D {};

class X {
static void foo();
};

static_assert(HasStaticFoo<S>::value, "Ouch");
static_assert(!HasStaticFoo<D>::value, "Ouch");
static_assert(!HasStaticFoo<X>::value, "Ouch");

int main() {}

with a recent C++0x capable compiler and you will notice
that the error for X is *not* related to a violation of static_assert.

is_constructible and is_convertible are traits that *require*
compiler support, because they were intended to cope with
access restrictions. I haven't tested recent implementations
but I would expect that most of them still behave like normal
SFINAE-based realizations, which - strictly speaking - are
non-conforming, but the easiest to do for the moment.

HTH & Greetings from Bremen,

Daniel Krügler

Nikolay Ivchenkov

unread,
Apr 14, 2010, 2:45:45 PM4/14/10
to
On 14 Apr, 15:19, Daniel Krügler <daniel.krueg...@googlemail.com>
wrote:

> Notice that the specification
> describes a context that would require public access
> in all cases.

Where exactly the specification describes the context where expression
CE is supposed to appear?

> > So, if the value of expression
> > "std::is_constructible<X, int>::value" depends on the constructor's
> > accessibility in a given context, then is_constructible is actually
> > not a part of the library, instead it shall be considered as core
> > language construct like dynamic_cast.
>
> Well, this is a stylistic question, and does not have any
> normative impact. If the library defines semantics of
> components that does not work without compiler-support,
> this is maybe odd but nothing fundamentally wrong.

A library component may be _implemented_ by the use of some specific
compiler support. But any _usage_ of a library component shall obey
the usual rules.

On 14 Apr, 15:18, Mathias Gaunard <loufo...@gmail.com> wrote:
> On 13 avr, 22:57, Daniel Krügler <daniel.krueg...@googlemail.com>
> wrote:
>
> > std::is_constructible
> > needs compiler-support
>
> Not necessarily.
> You could use SFINAE to check whether the expressions that defines
> is_contructible's value are well-formed.

SFINAE does not apply to violation of the access rules. See N3092 -
14.8.2/8:

"If a substitution results in an invalid type or expression, type
deduction fails. An invalid type or expression is one that would be
ill-formed if written using the substituted arguments. Access checking
is not done as part of the substitution process. Consequently, when
deduction succeeds, an access error could still result when the
function is instantiated."

Daniel Krügler

unread,
Apr 14, 2010, 7:39:30 PM4/14/10
to
On 14 Apr., 20:45, Nikolay Ivchenkov <ts...@mail.ru> wrote:
> On 14 Apr, 15:19, Daniel Krügler <daniel.krueg...@googlemail.com>
> wrote:
>
> > Notice that the specification
> > describes a context that would require public access
> > in all cases.
>
> Where exactly the specification describes the context where expression
> CE is supposed to appear?

There is no specific context described. If there is no specific
context given, it must work everywhere - assuming the hypothetical
function template create is available as well as the involved
types. The last requirement follows from the "precondition"
column in table 45 — Type property predicates:

"T and all types in the parameter pack Args shall be complete types,
(possibly cv-qualified) void, or arrays of unknown bound."

> > > So, if the value of expression
> > > "std::is_constructible<X, int>::value" depends on the constructor's
> > > accessibility in a given context, then is_constructible is actually
> > > not a part of the library, instead it shall be considered as core
> > > language construct like dynamic_cast.
>
> > Well, this is a stylistic question, and does not have any
> > normative impact. If the library defines semantics of
> > components that does not work without compiler-support,
> > this is maybe odd but nothing fundamentally wrong.
>
> A library component may be _implemented_ by the use of some specific
> compiler support. But any _usage_ of a library component shall obey
> the usual rules.

The standard does not require this. It is sufficient that
the library component describes the effects, and the
preconditions of usage. If you find that these violate
the C++ core language rules you either have found a
defect in the specification or you found a "support"
component.

HTH & Greetings from Bremen,

Daniel Krügler


Nikolay Ivchenkov

unread,
Apr 15, 2010, 2:54:30 AM4/15/10
to
On 15 Apr, 03:39, Daniel Krügler <daniel.krueg...@googlemail.com>
wrote:

> On 14 Apr., 20:45, Nikolay Ivchenkov <ts...@mail.ru> wrote:
>
> > Where exactly the specification describes the context where expression
> > CE is supposed to appear?
>
> There is no specific context described.

So, why shall contexts where private constructor is accessible be
discarded from consideration?

> If there is no specific
> context given, it must work everywhere

Is it supposed to be obvious implicit rule?

Daniel Krügler

unread,
Apr 16, 2010, 3:01:17 PM4/16/10
to
On 15 Apr., 08:54, Nikolay Ivchenkov <ts...@mail.ru> wrote:
> On 15 Apr, 03:39, Daniel Kr�gler <daniel.krueg...@googlemail.com>

> wrote:
>
> > On 14 Apr., 20:45, Nikolay Ivchenkov <ts...@mail.ru> wrote:
>
> > > Where exactly the specification describes the context where expression
> > > CE is supposed to appear?
>
> > There is no specific context described.
>
> So, why shall contexts where private constructor is accessible be
> discarded from consideration?
>
> > If there is no specific
> > context given, it must work everywhere
>
> Is it supposed to be obvious implicit rule?

I would say, that this is probably a border-case and the wording
could (and should) be improved. I'm going to file this as an NB
comment.

Thanks & Greetings from Bremen,

Daniel Kr�gler

Nikolay Ivchenkov

unread,
Apr 17, 2010, 6:03:35 PM4/17/10
to
On 16 Apr, 23:01, Daniel Krügler <daniel.krueg...@googlemail.com>
wrote:

> I would say, that this is probably a border-case and the wording
> could (and should) be improved. I'm going to file this as an NB
> comment.

Note that publicity of _any_ member function directly invoked by the
expression CE should be checked.

#include <iostream>
#include <type_traits>

class A
{
~A() {} // private
};

class B
{
public:
B() {}
private:
B(B const &) {}
};

class C
{
operator int() { return 0; } // private
};

struct X
{
X(A) {}
X(B) {}
X(int) {}
};

int main()
{
std::cout << std::is_constructible<X, A>::value;
std::cout << std::is_constructible<X, B>::value;
std::cout << std::is_constructible<X, C>::value;
}

Note also that well-formed expression could render the program ill-
formed. In particular, the program is ill-formed if an undefined
function is used:

#include <type_traits>

struct Y
{
Y(int); // has no definition
};

int main()
{
// OK
!std::is_constructible<Y, int>::value ?
(void)Y(0) : (void)sizeof Y(0);

// renders the program ill-formed
std::is_constructible<Y, int>::value ?
(void)Y(0) : (void)sizeof Y(0);
}

So, it is very important to distinguish "ill-formed program" and "ill-
formed expression". However, there are several places in the
specification where the notion of ill-formedness applies to entire
program when it presumably should apply to a certain construct. For
example:

4.10/3:
"A prvalue of type "pointer to cv D", where D is a class type, can be
converted to a prvalue of type "pointer to cv B", where B is a base
class (Clause 10) of D. If B is an inaccessible (Clause 11) or
ambiguous (10.2) base class of D, a program that necessitates this
conversion is ill-formed."

This wording doesn't say that an expression or an initialization which
necessitates ambiguous derived-to-base pointer conversion is ill-
formed. How shall std::is_constructible handle this case?

#include <iostream>
#include <type_traits>

struct B {};

struct B1 : B {};
struct B2 : B {};

struct D : B1, B2 {};

int main()
{
std::cout << std::is_constructible<B *, D *>::value;
}

Another example:

8.5/8:
"A program that calls for default-initialization or value-
initialization of an entity of reference type is ill-formed."

What is the result of the following program?

#include <iostream>
#include <type_traits>

int main()
{
std::cout << std::is_constructible<int &>::value;
}

The same questions should be addressed to the use of SFINAE principle
that is also based on the notion of ill-formedness of a particular
constructs.

Nikolay Ivchenkov

unread,
Apr 18, 2010, 11:12:28 AM4/18/10
to
On 18 Apr, 02:03, Nikolay Ivchenkov <ts...@mail.ru> wrote:
>
> #include <type_traits>
>
> struct Y
> {
> Y(int); // has no definition
> };
>
> int main()
> {
> // OK
> !std::is_constructible<Y, int>::value ?
> (void)Y(0) : (void)sizeof Y(0);
>
> // renders the program ill-formed
> std::is_constructible<Y, int>::value ?
> (void)Y(0) : (void)sizeof Y(0);
> }

Apparently, my conclusion about the first case

> // OK
> !std::is_constructible<Y, int>::value ?
> (void)Y(0) : (void)sizeof Y(0);

was wrong. The second operand of the conditional operator is not an
unevaluated operand here, so it is potentially evaluated, and the
constructor is still used. The comments in the following example
should be correct:

#include <type_traits>

struct Y
{
// has no definition
Y(int);
};

template <bool b>
void use()
{


// renders the program ill-formed

static_cast<Y>(0);
}

template <>
void use<false>()
{
// OK
sizeof static_cast<Y>(0);
}

int main()
{
// calls use<false>
use<!std::is_constructible<Y, int>::value>();

// calls use<true>
use<std::is_constructible<Y, int>::value>();

Daniel Krügler

unread,
Apr 19, 2010, 12:46:35 AM4/19/10
to

Sure.

> Note also that well-formed expression could render the program ill-
> formed. In particular, the program is ill-formed if an undefined
> function is used:
>
> #include <type_traits>
>
> struct Y
> {
> Y(int); // has no definition
> };
>
> int main()
> {
> // OK
> !std::is_constructible<Y, int>::value ?
> (void)Y(0) : (void)sizeof Y(0);
>
> // renders the program ill-formed
> std::is_constructible<Y, int>::value ?
> (void)Y(0) : (void)sizeof Y(0);
> }

[I'm referring to your revised version in the
following]

Not really. Your test program which contains

use<!std::is_constructible<Y, int>::value>();

is ill-formed, because the definition of the
constructor is *missing*, but it is *not* ill-
formed, because an undefined entity is *used*.
Note that this is an important difference and
it ensures that the definition of is_constructible
and is_convertible based on a well-formed
expression "works" even if any of the used
entities is potentially not defined. This
also means that these support trait definitions
are neutral versus potentially missing definitions,
which is exactly intended (otherwise they would
not be implementable).

3.2/3:

"Every program shall contain exactly one definition
of every non-inline function or variable that is
used in that program; no diagnostic required. [..]"

> So, it is very important to distinguish "ill-formed program" and "ill-
> formed expression".

Not really. The core language does not distinguish
between a well/ill-formed program or a well/ill-
formed expression. If the core language speaks
if an ill-formed expression this is just a short
wording of "A program that contains this expression
is ill-formed".

There is no problem with all of these examples, they
are covered by the definition of an ill-formed
expression equivalent being to an ill-formed program.

> The same questions should be addressed to the use of SFINAE principle
> that is also based on the notion of ill-formedness of a particular
> constructs.

For the same reasons as mentioned above this is
also no special problem for SFINAE, based on
14.9.2/8. A simple way of describing how
is_constructible and is_convertible are defined
is to say: "Just like SFINAE, except that access
checking is done" (We already have agreed that
this access checking is supposed to be context-free
and is intended to allow for public access only).

HTH & Greetings from Bremen,

Daniel Krügler

Nikolay Ivchenkov

unread,
Apr 19, 2010, 2:59:52 PM4/19/10
to
On 19 Apr, 08:46, Daniel Kru"gler <daniel.krueg...@googlemail.com>
wrote:

> On 18 Apr., 00:03, Nikolay Ivchenkov <ts...@mail.ru> wrote:
>
> > Note also that well-formed expression could render the program ill-
> > formed. In particular, the program is ill-formed if an undefined
> > function is used:
>
> > #include <type_traits>
>
> > struct Y
> > {
> > Y(int); // has no definition
> > };
>
> > int main()
> > {
> > // OK
> > !std::is_constructible<Y, int>::value ?
> > (void)Y(0) : (void)sizeof Y(0);
>
> > // renders the program ill-formed
> > std::is_constructible<Y, int>::value ?
> > (void)Y(0) : (void)sizeof Y(0);
> > }
>
> [I'm referring to your revised version in the
> following]
>
> Not really. Your test program which contains
>
> use<!std::is_constructible<Y, int>::value>();
>
> is ill-formed, because the definition of the
> constructor is *missing*, but it is *not* ill-
> formed, because an undefined entity is *used*.

Both conditions are important - unless the following wording applies:
"If no valid specialization can be generated for a template
definition, and that template is not instantiated, the template
definition is ill-formed, no diagnostic required." - in this case I'm
not sure, but I can propose for consideration the following:

#include <type_traits>

struct Y
{
Y(int); // has no definition

Y(long) {}
};

template <int n>
void use()
{
// renders the program ill-formed when n==1
static_cast<Y>(
typename std::conditional<n==1, int, long>::type());
}

template <>
void use<0>()


{
// OK
sizeof static_cast<Y>(0);
}

int main()
{
// calls use<false>

use<!std::is_constructible<Y, int>::value>();

// calls use<true>
use<std::is_constructible<Y, int>::value>();
}

We can conclude that this program is ill-formed considering two facts
(but not only one of them):
1) the constructor Y(int) is used (so, it shall be defined), and
2) the definition of the constructor Y(int) is not provided.

These facts collectively can be expressed as "undefined function is
used".

Similarly, the following program

#include <typeinfo>

class X;

int main()
{
typeid(X);
}

is ill-formed because:
1) the class type X is used as the operand of the typeid operator
(i.e. in the context where it is required to be completely-defined),
and
2) the definition of the class X is not provided prior to the typeid
expression.

Is the expression typeid(X) well-formed?

> > So, it is very important to distinguish "ill-formed program" and "ill-
> > formed expression".
>
> Not really. The core language does not distinguish
> between a well/ill-formed program or a well/ill-
> formed expression. If the core language speaks
> if an ill-formed expression this is just a short
> wording of "A program that contains this expression
> is ill-formed".

I am talking about the inverse consequence: we can't strictly conclude
that an expession is ill-formed when the specification asserts ill-
formedness of the entire program only.

> There is no problem with all of these examples, they
> are covered by the definition of an ill-formed
> expression equivalent being to an ill-formed program.

Where is this definition? For the program

#include <type_traits>

struct B {};

struct B1 : B {};
struct B2 : B {};

struct D : B1, B2 {};

template <class T>
typename std::add_rvalue_reference<T>::type create();

typedef char one[1];
typedef char two[2];

template <class T>
one &f(char (*)[sizeof static_cast<T>(create<D *>())]);
template <class T>
two &f(...);

int main()
{
static_assert(sizeof f<int>(0) == sizeof(two), "");
static_assert(sizeof f<B *>(0) == sizeof(two), "");
}

GNU C++ 4.5 issues the following diagnostic message:
"error: 'B' is an ambiguous base of 'D'".

Presumably, the expression

sizeof static_cast<int>(create<D *>())

is treated as ill-formed, while the expression

sizeof static_cast<B *>(create<D *>())

is treated as well-formed (but the entire program is considered as ill-
formed). If you think that the behavior of the GNU C++ compiler is
wrong, how can you prove it?

Daniel Krügler

unread,
Apr 22, 2010, 9:09:30 AM4/22/10
to

This example doesn't matter in this discussion, because it does
not properly reflect the definition of std::is_constructible.
If you look at the definition of std::is_constructible, it obviously
does not describe a complete program, so we cannot argue
about possibly missing definitions of any potentially used
entity of this definition, because that cannot be deduced from
the definition. It was also intended *not* to use std::declval
instead of the seemingly near-to-equal create function template,
because we would have to consider the rules of ill-formed
code related to potentially evaluated call of std::declval (which
makes the code ill-formed). From the given spec we can also
not conclude that the definition of the used specialization
of the function template create is missing which otherwise
would be a reason to make a completed program ill-formed.

In other words: Any *additional* programming errors contained
in a program using std::is_constructible (like any missing
definition of a used entity) does not affect the definition of the
outcome of this type-trait - These kinds of violations can still
be ill-formed with an unspecified location/point in time, when
this will be diagnosed, following the basic rules described in
sub-clause 1.4 [intro.compliance].

> Similarly, the following program
>
> #include <typeinfo>
>
> class X;
>
> int main()
> {
> typeid(X);
> }
>
> is ill-formed because:
> 1) the class type X is used as the operand of the typeid operator
> (i.e. in the context where it is required to be completely-defined),
> and
> 2) the definition of the class X is not provided prior to the typeid
> expression.
>
> Is the expression typeid(X) well-formed?

It is clearly ill-formed, but that does not affect in any case
the situation with is_constructible and is_convertible. It could
potentially be, if typeid would be involved within the definition
of either of these compiler support traits.

> > > So, it is very important to distinguish "ill-formed program" and "ill-
> > > formed expression".
>
> > Not really. The core language does not distinguish
> > between a well/ill-formed program or a well/ill-
> > formed expression. If the core language speaks
> > if an ill-formed expression this is just a short
> > wording of "A program that contains this expression
> > is ill-formed".
>
> I am talking about the inverse consequence: we can't strictly conclude
> that an expession is ill-formed when the specification asserts ill-
> formedness of the entire program only.

I don't see how this affects the definition of is_constructible,
as shown above. The definition itself only shows a partial
program, so users can still make an ill-formed program
and cannot expect that the pure existence of std::is_constructible
make the code well-formed and would only affect the outcome
of the evaluation of std::is_constructible</types/>::value.

This is a compiler defect, because it violates 14.9.2 [temp.deduct].
Unless the compiler vendor argues that this behaviour is intended
because of his/her reading of the core language this should be
considered as a compiler bug. I have at least weak evidence that
this my interpretation is agreed on as a compiler defect based on
an *informal* query to Jason Merrill. I suggest to open a gcc bug
entry for this and let's see to what kind of further insight this may
lead.

HTH & Greetings from Bremen,

Daniel Krügler

--

Nikolay Ivchenkov

unread,
Apr 22, 2010, 7:22:24 PM4/22/10
to
On 22 Apr, 17:09, Daniel Krugler <daniel.krueg...@googlemail.com>
wrote:

> On 19 Apr., 20:59, Nikolay Ivchenkov <ts...@mail.ru> wrote:
>
> > We can conclude that this program is ill-formed considering two facts
> > (but not only one of them):
> > 1) the constructor Y(int) is used (so, it shall be defined), and
> > 2) the definition of the constructor Y(int) is not provided.
>
> > These facts collectively can be expressed as "undefined function is
> > used".
>
> This example doesn't matter in this discussion, because it does
> not properly reflect the definition of std::is_constructible.
> If you look at the definition of std::is_constructible, it obviously
> does not describe a complete program, so we cannot argue
> about possibly missing definitions of any potentially used
> entity of this definition, because that cannot be deduced from
> the definition.

The problem is that the same I could say, for example, about
"std::is_constructible<int &>::value": it is stated that if the
reference is value-initialized then the entire program is ill-formed,
but the expression T(), where T is "lvalue reference to int", could
still be considered as well-formed unless otherwise specified
somewhere else.

> In other words: Any *additional* programming errors contained
> in a program using std::is_constructible (like any missing
> definition of a used entity)

What is the "additional programming error"? For example:

#include <typeinfo>

class X;

int main()
{
typeid(X);
}

May the missing definition of X be considered as "additional
programming error"?

> > Similarly, the following program
>
> > #include <typeinfo>
>
> > class X;
>
> > int main()
> > {
> > typeid(X);
> > }
>
> > is ill-formed because:
> > 1) the class type X is used as the operand of the typeid operator
> > (i.e. in the context where it is required to be completely-defined),
> > and
> > 2) the definition of the class X is not provided prior to the typeid
> > expression.
>
> > Is the expression typeid(X) well-formed?
>
> It is clearly ill-formed

It is not obvious.

> but that does not affect in any case
> the situation with is_constructible and is_convertible.

Are you sure?

#include <iostream>
#include <type_traits>
#include <typeinfo>

class X;

struct Y
{
template <class T>
Y(T &, std::type_info const & = typeid(T));
};

int main()
{
std::cout << std::is_constructible<Y, X &>::value;
}

> > I am talking about the inverse consequence: we can't strictly conclude
> > that an expession is ill-formed when the specification asserts ill-
> > formedness of the entire program only.
>
> I don't see how this affects the definition of is_constructible,
> as shown above.

Directly. If in some particular case the ill-formedness of the
expression CE is unspecified then the respective value of
std::is_constructible<TYPES>::value is also unspecified.

> > If you think that the behavior of the GNU C++ compiler is
> > wrong, how can you prove it?
>
> This is a compiler defect, because it violates 14.9.2 [temp.deduct].

It is nude matter.

Daniel Krügler

unread,
Apr 24, 2010, 8:05:04 PM4/24/10
to
On 23 Apr., 01:22, Nikolay Ivchenkov <ts...@mail.ru> wrote:
> On 22 Apr, 17:09, Daniel Krugler <daniel.krueg...@googlemail.com>
> wrote:
>
> > On 19 Apr., 20:59, Nikolay Ivchenkov <ts...@mail.ru> wrote:
>
> > > We can conclude that this program is ill-formed considering two facts
> > > (but not only one of them):
> > > 1) the constructor Y(int) is used (so, it shall be defined), and
> > > 2) the definition of the constructor Y(int) is not provided.
>
> > > These facts collectively can be expressed as "undefined function is
> > > used".
>
> > This example doesn't matter in this discussion, because it does
> > not properly reflect the definition of std::is_constructible.
> > If you look at the definition of std::is_constructible, it obviously
> > does not describe a complete program, so we cannot argue
> > about possibly missing definitions of any potentially used
> > entity of this definition, because that cannot be deduced from
> > the definition.
>
> The problem is that the same I could say, for example, about
> "std::is_constructible<int &>::value": it is stated that if the
> reference is value-initialized then the entire program is ill-formed,
> but the expression T(), where T is "lvalue reference to int", could
> still be considered as well-formed unless otherwise specified
> somewhere else.

This discussions starts to cycle a bit. I tried to point out that
the definition of is_constructible refers to the ill-formed state
from a program that can be deduced from this partially defined
program. I also tried to make clear that any completing program
may contain constructs that would - in combination with this
partial program - still cause an ill-formed program, among
these reasons are that the completed program may omit
the definition of a used entity. These other defects are unrelated
to the definition of is_constructible - in particular unrelated
to the reference of an ill-formed expression within this partial
program. IMO the current wording is clear and unambiguous in
this regard.

Referring to your example of "std::is_constructible<int &>::value"
I do not see this problem, because the given

typedef int& T;

the construction

T()

does cause the program to be instantaneously ill-formed (note
that this assertion is unaffected by the point in term when such
an ill-formed program would produce the required diagnostic).
This situation cannot be compared with

struct X { X(int); };

applied to the construction

static_cast<X>(create<int>())

because the expression itself is potentially well-formed.
The fact that the program could be *potentially* ill-formed
if any definition of a used entity - like that of the
constructor X::X(int) or that of the specialization
int&& create<int>() - is missing can only be deduced from
a completed program. This means it is meaningless to
debate about the meaning of "ill-formed" within the
definition of is_constructible/is_convertible in regard
to these aspects.

> > In other words: Any *additional* programming errors contained
> > in a program using std::is_constructible (like any missing
> > definition of a used entity)
>
> What is the "additional programming error"? For example:
>
> #include <typeinfo>
>
> class X;
>
> int main()
> {
> typeid(X);
> }
>
> May the missing definition of X be considered as "additional
> programming error"?

I don't understand this example in combination with
the discussion of the definition of is_constructible.
It does not mimic the definition of is_constructible
and no use of is_constructible is in sight. If we would
try to bring this into the came, as in

#include <typeinfo>
#include <type_traits>

class X;

int main()
{
bool b = std::is_constructible<int&>::value;
typeid(X);
}

would still not help us. Neither does the ill-formed
program construction (created by the expression
typeid(X)) influence the definition of is_constructible,
nor does the definition of is_constructible make
this program well-formed. Concluding, I don't see
how this example points out a defect in the
definition of the compiler support trait.

> > > Similarly, the following program
>
> > > #include <typeinfo>
>
> > > class X;
>
> > > int main()
> > > {
> > > typeid(X);
> > > }
>
> > > is ill-formed because:
> > > 1) the class type X is used as the operand of the typeid operator
> > > (i.e. in the context where it is required to be completely-defined),
> > > and
> > > 2) the definition of the class X is not provided prior to the typeid
> > > expression.
>
> > > Is the expression typeid(X) well-formed?
>
> > It is clearly ill-formed
>
> It is not obvious.

According to [expr.typeid]/3:

"[..] If the type of the expression is a class type, the class
shall be completely-defined. [..]"

which is a requirement to the implementation to diagnose
a violation, see also [intro.compliance]/1+2.

> > but that does not affect in any case
> > the situation with is_constructible and is_convertible.
>
> Are you sure?
>
> #include <iostream>
> #include <type_traits>
> #include <typeinfo>
>
> class X;
>
> struct Y
> {
> template <class T>
> Y(T &, std::type_info const & = typeid(T));
> };
>
> int main()
> {
> std::cout << std::is_constructible<Y, X &>::value;
> }

Well, this is a good example that highlights a potential problem
in both support traits, but it is not related to typeid or to a
missing definition of a type. I construct a different example that
better shows the potential problem I see and does not depend
on missing definitions of entities:

struct X {
X() = delete;
};

struct Y {
template <class T> Y(T &, T const & = T());
};

int main() {
bool b = std::is_constructible<Y, X&>::value; // OK?
};

As of current language rules this is a non-SFINAE ill-formed
situation, because the defect does not occur in the "immediate
context of a function type and its template parameter types"
([temp.deduct]/8). Examples like this could also require
speculative compilation and this is a requirement that is
extremely hard to realize (E.g. it would also mean that some
instantiation error within some remote class template has to be
caught).

I agree that the current definition of both is_constructible
and is_convertible requires a compiler to handle them
without making the program ill-formed and this is probably
not realistic to implement for these cases. We may want to
restrict here to "the immediate context" as [temp.deduct]/8
does.

> > > I am talking about the inverse consequence: we can't strictly conclude
> > > that an expession is ill-formed when the specification asserts ill-
> > > formedness of the entire program only.
>
> > I don't see how this affects the definition of is_constructible,
> > as shown above.
>
> Directly. If in some particular case the ill-formedness of the
> expression CE is unspecified then the respective value of
> std::is_constructible<TYPES>::value is also unspecified.

I don't see the situation you tried to show above as a situation
of an unspecified ill-formedness. The usage of typeid(X) is clearly
ill-formed, similarly as that of sizeof(X) for an undefined entity X.
If you think that this is not clear from the standard, this is a
different issue, I believe.

> > > If you think that the behavior of the GNU C++ compiler is
> > > wrong, how can you prove it?
>
> > This is a compiler defect, because it violates 14.9.2 [temp.deduct].
>
> It is nude matter.

I don't know what this is supposed to mean. But the ambiguity
situation
you were referring to is clearly a normal SFINAE situation, because
char (*)[sizeof static_cast<T>(create<D *>()) is part of the template
parameter type and also within the "immediate context".

Greetings from Bremen,

Daniel Krügler


Nikolay Ivchenkov

unread,
Apr 26, 2010, 4:55:32 AM4/26/10
to
On 25 Apr, 04:05, Daniel Krugler <daniel.krueg...@googlemail.com>
wrote:

> On 23 Apr., 01:22, Nikolay Ivchenkov <ts...@mail.ru> wrote:
>
> This discussions starts to cycle a bit. I tried to point out that
> the definition of is_constructible refers to the ill-formed state
> from a program that can be deduced from this partially defined
> program. I also tried to make clear that any completing program
> may contain constructs that would - in combination with this
> partial program - still cause an ill-formed program, among
> these reasons are that the completed program may omit
> the definition of a used entity. These other defects are unrelated
> to the definition of is_constructible - in particular unrelated
> to the reference of an ill-formed expression within this partial
> program. IMO the current wording is clear and unambiguous in
> this regard.

There is a big difference between the current wording and the
following

[begin]


Given the following function prototype:

template <class T>
typename add_rvalue_reference<T>::type create();

the predicate condition for a template specialization
is_constructible<T, Args...> shall be satisfied if and only if

[removed]the following expression CE would be well-formed:[/removed]
[inserted]at the point of instantiation for the template
specialization the declaration of the form

typedef decltype(CE) unique_name;

would be well-formed, where unique_name is an identifier that differs
from any other identifier in the program, and CE is an expression of
the following form:[/inserted]
[...]

[inserted]For the purposes of this rule a declaration is considered to
be ill-formed if and only if it causes the entire program to be ill-
formed according to diagnosable rules and this fact can be established
without considering all parts of the program except the declaration
itself and the contiguous portion of program text between the
beginning of the translation unit where the declaration resides and
the declaration; otherwise for the purposes of this rule the
declaration is considered to be well-formed.[/inserted]
[/end]

which could be considered as evidence for your explanation (if my
understanding of your explanation is right) when 14.6.4.1 [temp.point]
is properly modified as shown below:

[begin]
For a class template specialization, a class member template
specialization, or a specialization for a class member of a class
template, if the specialization is implicitly instantiated because it
is referenced from within another template specialization, if the
context from which the specialization is referenced depends on a
template parameter, and if the specialization is not instantiated
previous to the instantiation of the enclosing template, the point of
instantiation is immediately before the point of instantiation of the
enclosing template. [inserted]Otherwise, if any template type argument
of such a specialization is a local type, the point of instantiation
for the specialization immediately precedes the innermost statement
that refers to the specialization.[/inserted] Otherwise, the point of
instantiation for such a specialization immediately precedes the
namespace scope declaration or definition that refers to the
specialization.

A local type is a local class or enumeration, or a type compounded
from at least one local type, or a class template specialization where
at least one template type argument is a local type.
[/end]
(Certain cases may still be uncovered)

Example 1:

#include <iostream>
#include <type_traits>
#include <typeinfo>

struct A
{
template <class T, class U = decltype(typeid(T))>
A(T *);
};

int main()
{
class X;
// the point of instantiation should be here
std::cout << std::is_constructible<A, X *>::value;
class X {};
}

Example 2:

#include <iostream>
#include <type_traits>
#include <typeinfo>

struct A
{
template <class T, class U = decltype(typeid(T))>
A(T *);
};

int main()
{
class X;
class X {};
// the point of instantiation should be here
std::cout << std::is_constructible<A, X *>::value;
}

> Referring to your example of "std::is_constructible<int &>::value"
> I do not see this problem, because the given
>
> typedef int& T;
>
> the construction
>
> T()
>
> does cause the program to be instantaneously ill-formed

I didn't understand this. What does "instantaneously ill-formed" mean?

> This situation cannot be compared with
>
> struct X { X(int); };
>
> applied to the construction
>
> static_cast<X>(create<int>())
>
> because the expression itself is potentially well-formed.

I could say that an expression in which undefined function or object
is used is always ill-formed. In the following example

int f();

int main()
{
sizeof f(); // (1)
f(); // (2)
}

the former expression f() is not potentially-evaluated, but the latter
expression f() is potentially-evaluated, so, these expressions can be
considered as non-equivalent.

#include <typeinfo>

int main()
{
class X;
typeid(X); // renders the program ill-formed

class X {};
typeid(X); // OK
}

According to your opinion, the former expression typeid(X) is ill-
formed, but the latter expression typeid(X) is well-formed. In any
case we shall consider a context where the expression resides.

> The fact that the program could be *potentially* ill-formed
> if any definition of a used entity - like that of the
> constructor X::X(int) or that of the specialization
> int&& create<int>() - is missing can only be deduced from
> a completed program.

In some particular cases similar ill-formedness can be determined
immediately:

int main()
{
struct Local
{
Local();
// cannot be defined outside
};

Local();
// renders the program ill-formed;
// it could be diagnosed right here
}

> > What is the "additional programming error"? For example:
>
> > #include <typeinfo>
>
> > class X;
>
> > int main()
> > {
> > typeid(X);
> > }
>
> > May the missing definition of X be considered as "additional
> > programming error"?
>
> I don't understand this example in combination with
> the discussion of the definition of is_constructible.

The well-formedness of a typeid expression with dependent operand
could affect the value of is_constructible<T, Args...>::value. For
example:

#include <iostream>
#include <type_traits>
#include <typeinfo>

class X;

struct A
{
template <class T, class U = decltype(typeid(T))>
A(T *);
};

int main()
{
std::cout << std::is_constructible<A, X *>::value;
// ?
}

If the well-formedness of the expression typeid(T), where T is
considered to be incomplete class type X, is unspecified then the
value of std::is_constructible<A, X *>::value is also unspecified.

> > > > Similarly, the following program
>
> > > > #include <typeinfo>
>
> > > > class X;
>
> > > > int main()
> > > > {
> > > > typeid(X);
> > > > }
>
> > > > is ill-formed because:
> > > > 1) the class type X is used as the operand of the typeid operator
> > > > (i.e. in the context where it is required to be completely-defined),
> > > > and
> > > > 2) the definition of the class X is not provided prior to the typeid
> > > > expression.
>
> > > > Is the expression typeid(X) well-formed?
>
> > > It is clearly ill-formed
>
> > It is not obvious.
>
> According to [expr.typeid]/3:
>
> "[..] If the type of the expression is a class type, the class
> shall be completely-defined. [..]"
>
> which is a requirement to the implementation to diagnose
> a violation, see also [intro.compliance]/1+2.

There is only one obvious consequence from the rules you showed: the
entire program is ill-formed. I asked you about the expression's well-
formedness.

> I don't see the situation you tried to show above as a situation
> of an unspecified ill-formedness. The usage of typeid(X) is clearly
> ill-formed, similarly as that of sizeof(X) for an undefined entity X.
> If you think that this is not clear from the standard, this is a
> different issue, I believe.

Yes, I think that this is not clear from the specification.
Defects of the C++ specification are supposed to be main subject here.

0 new messages