Re: Can a class type be considered both incomplete and abstract?
On Aug 2, 7:50 pm, Nikolay Ivchenkov <ts...@mail.ru> wrote:
Consider the following fragment of an existing std::tr1::is_function
implementation:
template<typename _Tp>
struct __in_array
: public __sfinae_types
{
private:
template<typename _Up>
static __one __test(_Up(*)[1]);
template<typename>
static __two __test(...);
public:
static const bool __value = sizeof(__test<_Tp>(0)) == 1;
};
template<typename _Tp>
struct is_function
: public integral_constant<bool, !(__in_array<_Tp>::__value
|| __is_union_or_class<_Tp>::value
|| is_reference<_Tp>::value
|| is_void<_Tp>::value)>
{ };
There is no intention to create an array object here. __in_array is
the helper that checks whether a type under test is a valid array
element type. If the check is successful then the type is not a
function.
But note that an unsuccessful test does not necessarily mean that the
type is a function - only that it might be a function type.
This implementation has two potential defects. First, the
following example probably becomes ill-formed:
#include <type_traits>
struct Abstract;
int main()
{
enum { value = std::tr1::is_function<Abstract>::value };
}
struct Abstract
{
virtual void f() = 0;
};
If the overload resolution selects "static __one __test(_Up(*)[1])" we
have the use of type Abstract(*)[1] which is presumably not a valid
type.
But in this case, Substitution Failure Is Not An Error (SFINAE). So
even though the compiler is not able to substitute "_Up" with
"Abstract" for one of the test__() function overloads (because the
resulting type would call for an array of abstract types), the
compiler reports no error. Instead, the compiler simply eliminates the
"__one __test(_Up(*)[1])" overload from the set of functions being
considered as the overload to handle the (hypothetical) one_test()
function call.
Now, if "Abstract" is an incomplete type at the point __in_array is
instantiated, the then type deduction does succeed. So for any
incomplete class type, __test(_Up(*)[1]) will be selected for the
overloaded function call. But since no array of "Abstract" types is
ever defined - this "error" is never subsequently detected. So
essentially, false positives are possible when __in_array tests an
incomplete type.
Fortunately, the is_function<> type trait does not rely exclusively on
__in_array<> to detect all class types. Instead, any abstract class
type for which __in_array::value does evaluate to false (and which
therefore indicates that the type is a potential function type) - is
caught by the subsequent __is_union_or_class<> test.
Second, the violation of the rule "If two different points of
instantiation give a template specialization different meanings
according to the one definition rule (3.2), the program is ill-formed"
relative to specialization __in_array<Abstract> is possible.
Yes, if __in_array were a user-implemented type-trait, then a user
program would have to be careful to apply __in_array to complete types
only. The implementation of the Standard Library, however, has to
ensure only that the user-level features of the library work as
documented. So, as long as is_function<> correctly identifies function
types, then the fact that an internal helper class template is not
always accurate poses no problem - since no user code is at all
adversely affected by its behavior.
So, the
correct solution looks like this:
template<typename _Tp, bool _Is_union_or_class>
struct __in_array_or_abstract
: public __sfinae_types
{
private:
template<typename _Up>
static __one __test(_Up(*)[1]);
template<typename>
static __two __test(...);
public:
static const bool value = sizeof(__test<_Tp>(0)) == 1;
};
template <class _Tp>
struct __in_array_or_abstract<_Tp, true>
{
static const bool value = true;
};
Presumably the purpose of the __in_array<> class template is to
determine whether objects of a particular type can be stored in an
array. Toward that end, the original __in_array<> was useful, since a
false negative was never possible, and a false positive was possible
only when __in_array tested an incomplete type.
In contrast, the revised __in_array, suggested above, is of absolutely
no help in determining whether a type can be stored in an array.
Because a "true" result (even when testing a complete type) means
either that the type can be stored in an array - or that the type
cannot be stored in an array (because the type is an abstract class
type).
Greg
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]