Is this legal code? It compiles in Comeau C++ in strict mode.
---------------
prerequisites:
1. a template meta-function 'is_built_in' that returns whether or not a type is
a
built-in type (including void)
2. a template meta-function 'is_ptr' which returns whether or not a type is a
pointer.
3. a template meta-function 'is_ref' which returns whether or not a type is a
reference.
4. a template meta-function 'is_array' which returns whether or not a type is
an array
5. a template meta-function 'is_ptr_to_mem' which returns whether or not a type
is a pointer-to-member (data member or member function)
6. (here is the annoying one) a template meta-function 'is_function' which
returns whether or not a type is a function type. (I have these generated by
the preprocessor up to 50 arguments). function pointers and function references
are caught by is_ptr and is_ref.
---------------
Basically, these are all of the types (that I can think of, let me know if I've
missed one.) that can't be in a pointer-to-member type: int X::* (except an
enumeration)
---------------
Here is my 'is_enum' implementation:
// is_enum
template<class T> struct is_enum {
private:
typedef char small;
struct large {
char _unused[256];
};
template<class U> static small check(void (U::*)(void));
template<class U> static large check(...);
public:
static const bool value =
sizeof(check<T>(0)) == sizeof(large)
&& ! is_built_in<T>::value
&& ! is_ptr<T>::value
&& ! is_ref<T>::value
&& ! is_array<T>::value
&& ! is_ptr_to_mem<T>::value
&& ! is_function<T>::value;
};
template<class T> const bool is_enum<T>::value; // def
// test.cpp --->
#include <iostream>
#include "the_above.h"
// sample enum
enum my_sample_enum { various, enumerated, values };
// sample user-defined type
struct my_sample_struct { };
template<class T> void test(void) {
std::cout << "type is enum? " << is_enum<T>::value << &std::endl;
return;
}
int main(int argc, char* argv[]) {
test<int>();
test<my_sample_enum>();
test<my_sample_struct>();
// etc.
return 0;
}
What do you think? Has this problem already been solved? I just read in Modern
C++ Design that there was no known way to detect if a type was an enum or not.
(in the TypeTraits part, I believe). Lastly, is this legal code? Or should the
psuedo-call to 'check' be ambiguous? I'm not sure, but this implementation
relies on the fact that 'void (some_enum::*)(void)' is an invalid type. Like I
said before, this code (plus the definition of the prerequisites above) compiles
with Comeau C++ is strict mode, and yields results as intended. (i.e. correct
results)
Paul Mensonides
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
[ about comp.lang.c++.moderated. First time posters: do this! ]
Cool, an is_class<T> implementation! Impressive.
> What do you think? Has this problem already been solved?
Yes, the boost type_traits library already has is_enum.
> I just read in Modern
> C++ Design that there was no known way to detect if a type was an enum or not.
> (in the TypeTraits part, I believe).
There is. An enum is a user-defined type that is convertible to int
without an user-defined conversion (and two user-defined conversions
in a row are illegal, so you can detect that.) See
http://www.boost.org/boost/type_traits/composite_traits.hpp
for an implementation (by John Maddock, who invented it AFAIK.)
> Lastly, is this legal code? Or should the
> psuedo-call to 'check' be ambiguous? I'm not sure, but this implementation
> relies on the fact that 'void (some_enum::*)(void)' is an invalid type.
It's legal; see 14.8.2/2.
Oops, I forgot is_array_incomplete.
Paul Mensonides
As long as you have another is_enum, yes.
> > What do you think? Has this problem already been solved?
>
> Yes, the boost type_traits library already has is_enum.
I looked at it shortly, does it handle function types (not function pointer or
reference types)?
> > I just read in Modern
> > C++ Design that there was no known way to detect if a type was an enum or
not.
> > (in the TypeTraits part, I believe).
>
> There is. An enum is a user-defined type that is convertible to int
> without an user-defined conversion (and two user-defined conversions
> in a row are illegal, so you can detect that.) See
Ah, yes. I see what you mean. Force double user-defined conversion.
> http://www.boost.org/boost/type_traits/composite_traits.hpp
>
> for an implementation (by John Maddock, who invented it AFAIK.)
>
> > Lastly, is this legal code? Or should the
> > psuedo-call to 'check' be ambiguous? I'm not sure, but this implementation
> > relies on the fact that 'void (some_enum::*)(void)' is an invalid type.
>
> It's legal; see 14.8.2/2.
Good, I like mine better, it's not as messy.
Paul Mensonides
I looked at 14.8.2/2 (indicated by Peter) and wondered if it is
possible to use Paul technique in order to detect whether some type T
contains some type member.
This is known challenge by Andrei Alexandrescu (look for post named
"Reiterated question").
The code might look like the following:
//
// detects if some type T has a type member named key_type
//
template<typename T>
struct has_key_type
{
private:
typedef char (&yes)[1];
typedef char (&no) [2];
template<class U> static yes check(typename U::key_type *);
template<class U> static no check(...);
public:
static const bool result =
sizeof(check<T>(0)) == sizeof(yes);
};
struct A1 {};
struct A2 { typedef int key_type; };
typedef int test_A1[has_key_type<A1>::result]; // fail
typedef int test_A2[has_key_type<A2>::result]; // ok ?
I tested this code with the online Comeau C/C++ 4.3 BETA#2 strict mode
and it compiled as expected (?). When commenting out the test_A1 line
the code compiled fine.
is the above code is legal in standard C++?
Thanks,
Rani
It compiles with Comeau 4.2.45.2c beta #5 also. I'm not sure if it is legal,
but is seems to me that it should be. The entire reason that I came up with it,
is that it would be ridiculous for something to be an error, if the overload
mechanism itself causes a declaration to be an error.
Say that I have some function 'f' somewhere:
template<class T> void f(int x);
Say that in another totally separate place I have another 'f':
template<class T> void f(void (T::*)(void));
Now, if overload resolution doesn't remove non-sensical declarations from the
overload set, I would not be able to call the first f with anything but a class
type.
int main(int argc, char* argv) {
f<int>(0);
return 0;
}
I though of this while I was trying to figure out a way to cause the compiler
*not* to error based on a non-sensical declaration, but instead choose something
else. This led to my original 'is_enum' implementation, which I didn't know
what already solved in the Boost library. So I re-wrote 'is_enum', and
re-engineered the old is_enum to be an 'is_class', thanks to the suggestion of
Peter Dimov earlier in this thread.
Actually, I already integrated into an parameter list type extraction utility
that I have been using, and it works like a charm. Good idea.
Paul Mensonides
No, no need for is_enum. The quoted code is a complete implementation
of is_class. You are using elimination to implement is_enum given
is_class, whereas boost.type_traits does the opposite.
> > Yes, the boost type_traits library already has is_enum.
>
> I looked at it shortly, does it handle function types (not function pointer or
> reference types)?
I believe it does.
Amazingly, it think that it is. You are the proud inventor of the
has_nested_type<> technique. :-)
>
> Good, I like mine better, it's not as messy.
>
Show it to us, if possible, please.
We like to learn.
Markus
I wonder whether this might also useful in writting a default_constructible
traits class:
template <typename T>
struct default_constructible
{
private:
typedef char (&yes)[1];
typedef char (&no)[2];
template <unsigned> struct size_type_wrapper { typedef T type; };
template <class U> static yes check( typename size_type_wrapper<
sizeof( new T ) >::type * );
template <class U> static no check( ... );
public:
static const bool result =
sizeof( check<T>(0) ) == sizeof(yes);
};
template <bool Val> struct static_assert;
template <> struct static_assert<true> {};
struct A { };
struct B { B(int) {} };
int main()
{
static_assert< default_constructible< A >::result >();
static_assert< !default_constructible< B >::result >();
}
Comeau 4.3 BETA#2 refuses to compile it saying,
"6478.c", line 10: error: no default constructor exists for class "B"
template <class U> static yes check( typename size_type_wrapper<
sizeof( new U ) >::type * );
Is this correct? I would have thought that as new B is illegal, sizeof( new
B ) would be, and so typename size_type_wrapper< sizeof( new T ) >::type
ought to be illegal too.
--
Richard Smith
The first paragraph of 14.8.3 (in particular, this sentence: "... If,
for a given function template, argument deduction fails, no such
function is added to the set of candidate functions for that
template."), in conjunction with already mentioned here 14.8.2, para 2
clearly indicate that the above is legal - which is unbelievably cool
and exciting!
Of course, the real-world compilers spoil the excitement a bit. GCC
2.95.3-5 (don't have 3.1) and Borland C++ 5.5.1 (incorrectly) reject
the code with the complaint that 'A1' has no 'key_type' memeber, both
MSVC 6.5 and VC 7.0 refuse to compile 'check<T>(0)' expression (saying
"'T' : illegal use of this type as an expression"), and Metrowerks
CodeWarrior 7.2 compiles the code, but resolves the overloads
incorrectly, resulting in has_key_type<A1>::result == true. Of course,
that's what bug reports are for (I am going to report to Metrowerks,
and it would be cool if somebody reported to GCC team and Borland).
Yet, after a few minutes of tweaking I've managed to produce a version
that compiles cleanly (and gives the right answer) under Comeau C/C++
4.2.45.2 for MS_WINDOWS_x86, gcc 2.95.3-5, MSVC 6.5 and VC 7.0:
#include "boost/config.hpp"
typedef char (&yes)[1];
typedef char (&no) [2];
#if defined(BOOST_MSVC) && (BOOST_MSVC < 1300)
template< typename U > yes has_key_type_helper(U*, U::key_type*);
#else
template< typename U >
yes has_key_type_helper(U*, typename U::key_type*);
#endif
template< typename U > no has_key_type_helper(U*, ...);
template<typename T>
struct has_key_type
{
public:
enum {
value = sizeof(has_key_type_helper(static_cast<T*>(0), 0))
== sizeof(yes)
};
};
struct A1 {};
struct A2 { typedef int key_type; };
typedef int test_A1[!has_key_type<A1>::value];
typedef int test_A2[has_key_type<A2>::value];
CodeWarrior and Borland still fail, though.
--
Aleksey
It is.
> but is seems to me that it should be. The entire reason that I came up with it,
> is that it would be ridiculous for something to be an error, if the overload
> mechanism itself causes a declaration to be an error.
>
> Say that I have some function 'f' somewhere:
>
> template<class T> void f(int x);
>
> Say that in another totally separate place I have another 'f':
>
> template<class T> void f(void (T::*)(void));
>
> Now, if overload resolution doesn't remove non-sensical declarations from the
> overload set,
It does, see section 14.8.3 para 1: "... If, for a given function
template, argument deduction fails, no such function is added to the
set of candidate functions for that template."
> I would not be able to call the first f with anything but a class
> type.
>
> int main(int argc, char* argv) {
> f<int>(0);
> return 0;
> }
>
Yep.
> I though of this while I was trying to figure out a way to cause the compiler
> *not* to error based on a non-sensical declaration, but instead choose something
> else.
You and Rani Sharoni totally rock :).
--
Aleksey
My original version relied on the fact that enums cannot be in the 'object' in a
pointer-to-member signature, so it simply eliminated all of the other things
that won't match either, like pointers-to-members, pointers, references, arrays,
incomplate arrays, function types, and primitive types (including void).
template<class T> class is_enum {
private:
template<class U> static small_t check(void (U::*)(void));
template<class U> static large_t check(...);
public:
static const bool value = sizeof(check<T>(0)) == sizeof(small_t)
&& ! is_primitive<T>::value
&& ! is_array<T>::value
&& ! is_array_incomplete<T>::value
&& ! is_function<T>::value
&& ! is_ptr<T>::value
&& ! is_ref<T>::value
&& ! is_ptr_to_member<T>::value;
};
Which effectively, eliminates everything but classes, which Peter Dimov
observed, and converted to an 'is_class' implementation. Then Rani Sharoni made
another modification of the basic idiom that resulted in a
'has_nested_type_named_xxx' implementation (unfortunately, you can't parametize
the name of the possible nested class).
Ah, the joys of C++ and template meta-programming.
Which reminds me, how do you change the color of a Button on Win32?
( just kidding :) )
Paul Mensonides
Yeah, I see what you mean, but I was thinking about 'is_enum'. I didn't look to
deeply into the boost version, and this is what I came up with.
template<class T> class is_enum {
private:
template<class U, bool =
is_primitive<U>::value
|| is_array<U>::value
|| is_array_incomplete<U>::value
|| is_function<U>::value >
struct helper {
operator U(void);
};
template<class U> struct helper<U, true> { };
static small_t check(int);
static large_t check(...);
public:
static const bool value
= sizeof(check(helper<T>())) == sizeof(small_t);
};
template<class T> const bool is_enum<T>::value;
Which doesn't use my original mechanism at all, just the
double-user-defined-conversion idea. Unfortunately, you still have to eliminate
types that can't be used as return types (or parameter types), like function
types. That is the really annoying thing, because C++ has no mechanism to
produce linearized lists--i.e. functions that have arbitrary numbers of
parameters. I'm using the preprocessor to generate 'is_function' partial
specializations, but given the unholy case of a function type with more than,
say, 50 parameters, this version above will fail. In other words, it is not
bulletproof.
> > > Yes, the boost type_traits library already has is_enum.
> >
> > I looked at it shortly, does it handle function types (not function pointer
or
> > reference types)?
>
> I believe it does.
Okay, I'll look closer. Right now, I'm looking at what I can do with template
template parameters, and deducing the numbers and types of parameters that they
have. Which could be passed arbitrarily to another template by a wrapper class.
e.g....
template<int I> struct sizer {
char value[I];
};
// no impl.
template<template<class> class T>
sizer<1> binding(T<int>*);
template<template<class, class> class T>
sizer<2> binding(T<int, int>*);
template<template<class, class, class> class T>
sizer<3> binding(T<int, int, int>*);
// etc.
int main(int argc, char* argv[]) {
std::cout << sizeof(binding<std::vector>(0).value) << &std::endl;
std::cout << sizeof(binding<std::basic_string>(0).value) << &std::endl;
}
Also, I'm working on partial binding.
template<int> struct bind;
template<> struct bind<2> {
template<template<class, class> class result, class T> struct args {
template<class T1> struct type : result<T1, T> {
typedef result<T1, T> partial_bind;
// hoard of ctors, template ctors, and assignments
};
};
};
template<> struct bind<3> {
template<template<class, class, class> class result, class T> struct args {
template<class T1, class T2> struct type : result<T1, T2, T> {
typedef result<T1, T2, T> partial_bind;
// same as above
};
};
};
// etc.
template<class> struct def_param;
template<template<class> class T, class T1>
struct def_param<T<T1> > {
template<int, class dummy = null_t> struct result;
template<class dummy> struct result<1, dummy> : type_to_type<T1> { };
};
template<template<class, class> class T, class T1, class T2>
struct def_param<T<T1, T2> > {
template<int, class D = null_t> struct result;
template<class D> struct result<1, dummy> : type_to_type<T1> { };
template<class D> struct result<2, dummy> : type_to_type<T2> { };
};
// etc.
template<template<class> class container, class T> struct X {
container<T> col;
};
int main(int argc, char* argv[]) {
/* note: assuming no extra default args on containers */
// sample a
typedef bind<2>::args<
std::vector, def_param<std::vector<int> >::result<2>::type
> partial_bind; // gets std::allocator<int>
X<partial_bind::type> x;
x.col.push_back(10); // use
// sample b
typedef bind<3>::args<
std::basic_string, def_param<std::basic_string<char> >::result<3>::type
> pb2; // gets std::allocator<char>
typedef bind<2>::args<
pb2, def_param<std::basic_string<char> >::result<2>::type
> pb3; // gets std::char_traits<char>
X<pb3::type> x2;
x.col = "hello world!"; // use
return 0;
}
I can generate this 'linearization' type stuff with Cpp. The annoying part is
nested explicit specialization, and lack of template typedefs. In order to
properly partial bind a template template parameter, I need template typedefs.
I can map all possible constructor calls, operator= calls, etc, an inherit
everything else, but type A != type B, and also importantly, the super class
loses its 'explicit' constructors or they all become explicit. If the template
that you are sending a partially bound template to knows that it might get a
partially bound template, then it can bind through a typedef. It can even check
if a nested type of a certain name exists with the 'has_nested_type_named_xxx'
template and use it if it does:
template<class T> class has_nested_partial_bind {
private:
template<class U> static small_t check(typename U::partial_bind*);
template<class U> static large_t check(...);
public:
static const bool value = sizeof(check<T>(0)) == sizeof(small_t);
};
template<class T> const bool has_nested_partial_bind<T>::value;
template<class T, bool = has_nested_partial_bind<T>::value>
struct select_nested_partial_bind {
typedef T type;
};
template<class T> struct select_nested_partial_bind<T, true> {
typedef typename T::partial_bind type;
};
template<template<class> class container, class T> struct X {
typename select_nested_partial_bind<container<T> >::type col;
};
Now, 'col' actually *is* a vector or basic_string, etc.. On the other hand,
this is invasive, so it isn't a complete solution.
> Show it to us, if possible, please.
> We like to learn.
Ooops!
I overlooked You used 2 different from: headers.
Unfortunately mods were too fast this time,
so the cancel request (5 min later) was not successful.
Please forget my message.
I don't think that this is correct. Here are the exact conditions
(taken from 14.8.2/2) for such techniques:
Type deduction may fail for the following reasons:
Attempting to create an array with an element type that is void, a
function type, or a reference type, or attempting to create an array
with a size that is zero or negative.
Attempting to use a type that is not a class type in a qualified
name.
Attempting to use a type in the qualifier portion of a qualified
name that names a type when that type does not contain the specified
member, or if the specified member is not a type where a type is
required.
Attempting to create a pointer to reference type.
Attempting to create a reference to a reference type or a reference
to void.
Attempting to create "pointer to member of T" when T is not a class
type.
Attempting to perform an invalid conversion in either a template
argument expression, or an expression used in the function
declaration.
Attempting to create a function type in which a parameter has a type
of void.
Attempting to create a cv-qualified function type.
I can't find any condition that suits your needs.
But the last condition is quite interesting; I think that it can be
exploited in order to produce is_function<T>. The code might be
something like:
template<typename>
struct is_reference
{ static const bool result = false; };
template<typename T>
struct is_reference<T&>
{ static const bool result = true; };
//
// Detects if T is function type
//
template<typename T>
struct is_function
{
private:
typedef char (&yes)[1];
typedef char (&no) [2];
template<typename U> static no check(const U *);
template<typename > static yes check(...);
public:
static const bool result =
sizeof(check<T>(0)) == sizeof(yes)
&& !is_reference<T>::result;
};
typedef int test[ is_function<void(int,char)>::result];
typedef int test[!is_function<int>::result];
typedef int test[!is_function<int&>::result];
The is_reference is needed because attempting to create a pointer to
reference type also fails the type deduction.
I tested this code on Comeau C/C++ 4.3 BETA#2 strict mode and it
compile fine.
Using this is_function it is easy to implement the is_function_ptr
(detects pointer to function) and is_function_ref (detects reference
to function).
Paul, I think that this might save you some work and it's not limited
for 50 arguments functions.
Rani
[snip]
> I don't think that this is correct. Here are the exact conditions
> (taken from 14.8.2/2) for such techniques:
I'm not so sure. It also says, immediately preceding:
"If substitution in a template parameter or in the function type of the function
template results in an invalid type, type deduction fails."
What exactly is an "invalid type"? I think that is pretty open-ended. I take
that to mean "anything that that makes no sense for a certain type T but is
syntactically legal and semantically legal for some other type T." For
instance,
template<unsigned I> struct Z { };
template<class T> void f(Z<sizeof(new T)>* z);
^---------------^
Is this *not* an invalid type if T has no default ctor? It looks invalid to me.
The expression 'new T' is non-sensical therefore the type should be 'invalid'.
It then goes on to list cases where type deduction *may* fail. I don't know if
that means where type deduction is *allowed* to fail, excluding all others.
I think the template meta-function 'hackery' constitutes the minority case, and
a function like the one above, which is *legal* sometimes, could theoretically
totally invalidate the overload set.
Later, at 14.8.3 it says,
"If, for a given function template, argument deduction fails, no such function
is added to the set of candidate functions for the template."
Earlier, at 13.3.2 (Viable functions)
"From the set of candidate functions constructed for a given context (13.3.1), a
set of viable functions is chosen, from which the best function will be selected
by comparing argument conversion sequences for the best fit (13.3.3). The
selection of viable functions considers relationships between arguments and
function parameters other than the ranking of conversion sequences."
This is what I'm getting out of the above.... Given two overloaded templates:
struct X {
X(int, int); // no default ctor
};
template<unsigned I> struct Z { };
template<class T> void f(Z<sizeof(new T)>*) { }
// and
template<class T> void f(int) { };
Both of these functions are 'viable' candidates, regardless of conversion
sequences (even an exact match). So, unless T has a default ctor, supposedly
the entire resolution is bad, because 'supposedly' the first 'f' has not been
removed from the overload set for being non-sensical. Given the definitions
above, Comeau C++ compiles this line:
f<X>(0);
My question is why? What removed the first 'f' from the candidate functions?
Obviously, the int argument is an exact match and is the best function.
However, 'supposedly' nothing removed the first 'f' from the set of candidate
functions, which 'supposedly' the replacement of 'T' is supposed to happen
before overload resolution...
14.8.3
"For each function template, if the argument deduction and checking succeeds,
the template-arguments (deduced and/or explicit) are used to instantiate a
single function template specialization which is added to the candidate
functions set to be used in overload resolution."
According to the Table 9 (pg. 223) 'exact match' is a conversion sequence. It
seems that Comeau C++ (in strict mode) is bypassing the normal overload
mechanism because it knows that 'f(int)' will win.
Which is right? It seems to me that 'Z<sizeof(new T)>*' could be an 'invalid
type' if instantiated with a certain T, which would remove it from the candidate
set, which would allow 'f(int)' to be selected by the overload resolution
mechanism. Otherwise, Comeau C++ should have errored when attempting to
instantiate the first 'f' to be added to the candidate set then discarded by the
'better match' of int.
What do you think?
Paul Mensonides
Now this I like. Who knew what a poorly implemented 'is_enum' idea could turn
into an unbounded function-type checker. :)
Unfortunately, though this solves one problem, it doesn't solve a general
'function_traits' type of class. :( That requires partial specializations to
extract parameter types, etc.
Nevertheless, this is the work of genius, and it compiles and works on Comeau
C++ 4.2.45.2c Beta #5 also.
Paul Mensonides
P.S. Rani, see the thread "Template template parameters and function template
overload resolution" at comp.std.c++. There is a related type of problem there
with deducing nested template types, etc.
> "If substitution in a template parameter or in the function type of the function
> template results in an invalid type, type deduction fails."
>
> What exactly is an "invalid type"? I think that is pretty open-ended. I take
> that to mean "anything that that makes no sense for a certain type T but is
> syntactically legal and semantically legal for some other type T."
> [...]
> It then goes on to list cases where type deduction *may* fail. I don't know if
> that means where type deduction is *allowed* to fail, excluding all others.
I don't know what are the exact reasons for specifying such list in
14.8.2/2, but notice that is_class, has_key_type and is_pointer all
used some explicit mentioned case form that list and indeed compiled
with Comeau. I think that the standard is not very clear whether the
listed cases (14.8.2/2) are the only cases in which the deduction
fails in such fashion.
If you are right then this "close to" is_pod class could be
implemented:
template<typename T>
struct is_legal_union_member
{
private:
template<typename U> union UnionHelper { T pod_member; };
template<unsigned> struct Int2Type;
typedef char (&yes)[1];
typedef char (&no) [2];
template<typename U>
static yes check(Int2Type<sizeof(UnionHelper<T>)> *);
template<typename>
static no check(...);
public:
static const bool result =
sizeof(check<T>(0)) == sizeof(yes);
};
struct A2 { A2(); };
typedef int test[!is_legal_union_member<A2>::result];
I'm not sure that it's that easy.
Indeed as I expected Comeau C/C++ 4.3 BETA#2 rejected that code.
Notice that even when I replaced the … with an int the compiler still
rejected the code which is not very consistent with your example
(compiler bug?).
Rani
sizeof(sizer<1>) is not guaranteed to be different from
sizeof(sizer<2>); I use char (&) [N] which is guaranteed to have size
N.
> // no impl.
> template<template<class> class T>
> sizer<1> binding(T<int>*);
> template<template<class, class> class T>
> sizer<2> binding(T<int, int>*);
> template<template<class, class, class> class T>
> sizer<3> binding(T<int, int, int>*);
There is no need for the dummy pointer parameter; leave the argument
list empty to avoid the (rare) possibility where int is not a valid
template argument for T.
> Also, I'm working on partial binding.
>
> template<int> struct bind;
>
> template<> struct bind<2> {
> template<template<class, class> class result, class T> struct args {
> template<class T1> struct type : result<T1, T> {
> typedef result<T1, T> partial_bind;
> // hoard of ctors, template ctors, and assignments
> };
> };
> };
I'm not sure that I get it. Is this a compile time bind2nd, with a
'metafunction' expressed as a template template parameter? If so, you
might want to take a look at Alexey's (soon-to-be-in-Boost) MPL
library.
If you are familiar with Boost.Bind, you'll probably be interested in
my compile-time port (can do mpl::bind<F, T, _1>::type) as well.
[...]
> I can generate this 'linearization' type stuff with Cpp. The annoying part is
> nested explicit specialization, and lack of template typedefs.
The approach that MPL adopts is to use classes with nested templates
as metafunctions and _not_ template template parameters (as Loki
does.)
That is,
template<class T, class U> struct is_same; // is_same<T, U>::type
returns true_type or false_type
is expressed as
struct is_same
{
template<class T, class U> struct apply;
};
and invoked as is_same::template apply<T, U>::type. (Some use the term
'quoted metafunction' for this idiom.)
Now it's possible to write bind2nd without typedef templates:
template<class F, class A1> struct bind2nd
{
template<class A2> struct apply
{
typedef typename F::template apply<A1, A2>::type type;
};
};
<snip>
> It's legal; see 14.8.2/2.
Hmm, a very interesting section, it seems to have really useful
apllications. E.g:
template <class T, void (T::*)(T &)>
struct swap_tester
{
swap_tester(int );
};
template <class T>
class has_swap
{
typedef char (&yes)[10];
typedef char (&no)[1];
template <class U> static yes check(swap_tester<U, &U::swap>);
template <class> static no check(...);
public:
enum { result = sizeof(check<T>(0)) == sizeof(yes) };
};
// now std::swap could be written as
namespace Loki
{
template <int i>
struct Int2Type
{
enum { original = i };
};
} // namespace Loki
namespace std
{
template<class T>
inline void swap_helper(T& a, T& b, Loki::Int2Type<true>) // has swap
{
a.swap(b);
}
template<class T>
inline void swap_helper(T& a, T& b, Loki::Int2Type<false>) // doesn't
{
T tmp = a;
a = b;
b = tmp;
}
template<class T>
inline void swap(T& a, T& b)
{
swap_helper(a, b, Loki::Int2Type<has_swap<T>::result>());
}
} // namespace std
And the same could be done for detecting AddRef/Release, etc.
Artem Livshits
Brainbench MVP for C++
http://www.brainbench.com
--
Posted via Mailgate.ORG Server - http://www.Mailgate.ORG
[snip]
> I'm not sure that it's that easy.
What's wrong with it being 'that easy'? :)
> Indeed as I expected Comeau C/C++ 4.3 BETA#2 rejected that code.
> Notice that even when I replaced the with an int the compiler still
> rejected the code which is not very consistent with your example
> (compiler bug?).
The question remains, what is *supposed* to happen here:
template<unsigned> struct sample { };
struct X {
X(int, int); // no default ctor
};
template<class T> small_t check( sample<sizeof(new T)>* );
template<class T> large_t check( int );
int main() {
check<X>(0);
return 0;
}
You can't have it both ways, either template type deduction removes the first
'check' or it should be a compile-time error. I think that it would be a
dangerous mechanism to have it be a compile-time error, because just a
declaration like this could destroy an otherwise perfectly valid function
call--that is not ambiguous in the 'typical' sense.
More explicitly, if the above compiles, then so should this:
template<class T> small_t check( sample<sizeof(new T)>* );
template<class T> large_t check( ... );
int main() {
check<X>(0);
return 0;
}
Template type deduction is supposed to happen 'before' overload resolution, in
order to determine which instantiation of which template functions should be
added to the overload set. There is absolutely no way that the above can *not*
fail the type deduction checking, nor, if you take only the explicit reasons for
failure, there is no way that it *can* fail the explicit argument checking
phase. In either case, this is prior to overload resolution. So basically, it
should either invalidate the overload set entirely, or it should be gotten rid
of by failing template type deduction.
Obviously, I'm byassed toward having any non-sensical type caused by this
checking to eliminate the function before overload resolution. This would open
up a world of possibilities, since you can put almost anything inside 'sizeof'.
On a more general note, I don't think that this 'type' of programming should not
be considered as a valid use of the language. I.e. uses upon which decisions
are made by the committee(s). Rather I think the possibilities encourage
library writers to abstract their code even more a reduce coupling between the
library and library user.
I agree that I didn't expect this type of stuff to compile, but the 'check(int)'
surprises me. I think that this a clear cut case of the standard *not* being
explicit, because this is kind of like 'pre-overload resolution'-time, and in
this area it is *very* unclear except on the order in which things are
'supposed' to happen--at the very least that the result is the same 'as if'
things happened in this specific order. As far as Comeau C++ is concerned, I've
gotten all kinds of weird errors (sometimes they are just blantantly wrong) from
overload ambiguity to compiler assertions that fail. For instance, the
'has_nested_XXX' meta-function fails if you pass it a type that has a 'template'
named XXX instead of a regular class (or vice-versa):
template<class T> struct has_nested_XXX {
private:
template<class U> static small_t check(typename U::XXX*);
template<class U> static large_t check(...);
public:
static const bool value = sizeof(check<T>(0)) == sizeof(small_t);
};
template<class T> struct has_nested_template_XXX {
private:
template<class U> static small_t check(typename U::template XXX<int>*);
template<class U> static small_t check(typename U::template XXX<int,
int>*);
// etc.
template<class U> static large_t check(...);
public:
static const bool value = sizeof(check<T>(0)) == sizeof(small_t);
};
// static const def's here...
struct A {
struct XXX { };
};
struct B {
template<class> struct XXX { };
};
struct C { }; // no XXX at all
int main() {
std::cout
<< has_nested_XXX<A>::value << '\n'
// okay: returns 'true'
<< has_nested_XXX<B>::value << '\n'
// error: assertion failed
<< has_nested_XXX<C>::value << '\n'
// okay: returns 'false'
<< has_nested_template_XXX<A>::value << '\n'
// error: 'supposed' overload ambiguity
<< has_nested_template_XXX<B>::value << '\n' // okay
// okay: returns 'true'
<< has_nested_unary_template_XXX<C>::value << &std::endl;
// okay: returns 'false'
return 0;
}
I don't necessary trust Comeau (like I usually do) in this area--the
template-type-deduction-twilight-zone. I think that it is exibiting 'weirdo'
behavior. Obviously, this needs to be clarified, and I hope that it is done in
the more 'open-ended' fashion. :)
By the way, I got the above stuff to work properly, but it took a heck of a lot
of 'hackery'...
template<class> struct split { }; // no nested 'type'
template< template<class> class T, class T1 > struct split< T<T1> > {
struct type { };
};
template< template<class, class> class T, class T1, class T2 > struct split<
T<T1, T2> > {
struct type { };
};
// etc.
template<class T> class has_template_XXX {
private:
template<class U>
static small_t check(
typename split<
typename U::template XXX<null_t>
>::type*
);
template<class U>
static small_t check(
typename split<
typename U::template XXX<null_t, null_t>
>::type*
);
// etc.
template<class U> static large_t check(...);
public:
enum { value = sizeof(check<T>(0)) == sizeof(small_t) };
};
This seems to work, why (as opposed to the other one) I don't know. I haven't
tried to fix the 'has_nested_XXX' template to handle the odd-ball nested
template yet, but I suppose it could use partial specialization with a 'bool'
argument that uses the has_template_XXX as a default parameter:
template<class T, bool = has_template_XXX<T>::value> class has_nested_XXX_impl {
private:
template<class U> static small_t check(typename U::XXX*);
template<class U> static large_t check(...);
public:
enum { value = sizeof(check<T>(0)) == sizeof(small_t) };
};
template<class T> struct has_nested_XXX_impl<T, true> {
static const bool value = false;
};
template<class T> struct has_nested_XXX : has_nested_XXX_impl<T> { };
I don't know whether or not that will work; this seems to be a "cross your
fingers" area with Comeau C++. (Not that I'm knocking them, nothing else will
even get close.)
Paul Mensonides
I realize that there is no guarantee that there is a size difference,
but why is
there for the reference?
> > // no impl.
> > template<template<class> class T>
> > sizer<1> binding(T<int>*);
> > template<template<class, class> class T>
> > sizer<2> binding(T<int, int>*);
> > template<template<class, class, class> class T>
> > sizer<3> binding(T<int, int, int>*);
>
> There is no need for the dummy pointer parameter; leave the argument
> list empty to avoid the (rare) possibility where int is not a valid
> template argument for T.
I know. :) This was my mistake, my implementation doesn't have it, but
I was
copying out of my head. I have it encapsulated in a macro.
#define BINDING(NAME) (sizeof(binding<NAME>().value) - 1U)
I have a 'minus 1' so the array doesn't go to zero size for a
non-template-template-parameter.
I.e. it's more like this:
template<int I> struct sizer {
char unused[I + 1U]; // will change to 'char (&)[I + 1U]'
};
template<class> sizer<0> binding();
template<template<class> class> sizer<1> binding();
template<template<class, class> class> sizer<2> binding(); // etc.
Incidently, where would an 'int' not be valid? It uses a pointer, which
doesn't
require a complete type (granted, I realize that it is unnecessary), but
other
than that, the only thing I can think of is:
template<template<int> class T> sizer<1> binding( T<int>* ); // error
> > Also, I'm working on partial binding.
> >
> > template<int> struct bind;
> >
> > template<> struct bind<2> {
> > template<template<class, class> class result, class T> struct
args {
> > template<class T1> struct type : result<T1, T> {
> > typedef result<T1, T> partial_bind;
> > // hoard of ctors, template ctors, and assignments
> > };
> > };
> > };
>
> I'm not sure that I get it. Is this a compile time bind2nd, with a
> 'metafunction' expressed as a template template parameter? If so, you
> might want to take a look at Alexey's (soon-to-be-in-Boost) MPL
> library.
It is a compile-time 'bind2nd' for template-template-parameters. I'm
basically
using a mechanism that 'binds' forces a template-template-parameter that
takes
two arguments into one that takes only one. Actually, it's more of a
bind2nd,
bind3rd, bind4th, etc..
The fun part, is reconstruction of default parameters given an example
to work
from. Which I realize that is not a good idea in some cases.
For instance, basic_string has these parameters (i.e. no-extension
ones)...
class T, class traits = std::char_traits<T>, class Allocator =
std::allocator<T>
Given the instruction to reconstruct default parameters, the partial
binding
mechanism with pull apart the default arguments looking for (in this
case) 'T'.
So, for instance, a bind<3> will bind the third argument 'partially', so
when it
is actually instantiated with 'T' later (after it has been passed as a
template-template-parameter) it will still bind as 'std::allocator<T>'
rather
than 'std::allocator<int>' or whatever the example was.
> If you are familiar with Boost.Bind, you'll probably be interested in
> my compile-time port (can do mpl::bind<F, T, _1>::type) as well.
>
> [...]
>
> > I can generate this 'linearization' type stuff with Cpp. The
annoying part
is
> > nested explicit specialization, and lack of template typedefs.
>
> The approach that MPL adopts is to use classes with nested templates
> as metafunctions and _not_ template template parameters (as Loki
> does.)
I'm just doing meta-programming 'with' template-template-parameters
because I
haven't yet tried it. :) We have even fewer constructs for manipulating
them
then we do for regular 'types'. In other words, the solution is not
necessarily
for the purpose of a specific use, but more to serve its own end (and my
curiousity).
> That is,
>
> template<class T, class U> struct is_same; // is_same<T, U>::type
> returns true_type or false_type
>
> is expressed as
>
> struct is_same
> {
> template<class T, class U> struct apply;
> };
>
> and invoked as is_same::template apply<T, U>::type. (Some use the term
> 'quoted metafunction' for this idiom.)
>
> Now it's possible to write bind2nd without typedef templates:
>
> template<class F, class A1> struct bind2nd
> {
> template<class A2> struct apply
> {
> typedef typename F::template apply<A1, A2>::type type;
> };
> };
But this doesn't work if the destination is not expecting it. Take
Loki, for
instance, GenScatterHierarchy and GenLinearHierarchy require
template-template-parameters. That is a given, unless I modify Loki
myself just
for a 'specific' case. Each of these template-template-parameters take
a
certain number of parameters, this also is fixed. In other words, there
is
absolutely no way to pass the name of a template that takes more or less
than
the required number of parameters--unless I partial bind the
quote-unquote
template-type. This is not an example of what I'm using it for, just a
simple
example to show the direction I'm going. If the implementation 'knows'
that
there is a nested template class with a typedef inside the 'type' passed
to the
template, then obviously it can use that. But it cannot if it doesn't
(say, not
written by me, for example).
Besides, I'm just doing it for fun (for now). Also, how far I can go
deconstructing a default argument and rebuilding it is interesting.
Assuming for the moment that this case is legal (hopefully there might be a
more definitive answer in the thread on comp.std.c++), what about the
following example: should this compile?
template <template <class> class Expr, class T>
struct does_expr_compile
{
private:
typedef char (&yes)[1];
typedef char (&yes)[2];
template <int> struct enum_wrapper {};
template <class U> static yes check( enum_wrapper< Expr<U>::value >
* );
template <class U> static no check(...);
public:
enum { value = sizeof( check<T>(0) ) == sizeof(yes) };
};
template <class T>
struct default_construction
{
enum { value = sizeof( new T ) };
};
int main()
{
does_expr_compile< default_construction, T >::value;
}
If so, I think this gives us an arbitrary does-this-expression-compile
template. And the template metafunctions such as default_construction might
even be constructible using some sort of lambda type trickery. It would be
cool to be able to simply write something like,
does_expr_compile< _new< _T >, T >::value
--
Richard Smith
> I though of this while I was trying to figure out a way to cause the
compiler
> *not* to error based on a non-sensical declaration, but instead choose
something
> else. This led to my original 'is_enum' implementation, which I didn't
know
> what already solved in the Boost library. So I re-wrote 'is_enum', and
> re-engineered the old is_enum to be an 'is_class', thanks to the
suggestion of
> Peter Dimov earlier in this thread.
Just for sake of interest, there is a second technique that one can use to
this sort of 'is this non-sensical?' compile time testing: it uses partial
specialisation instead of overloading, and is less clean, but it is
conceivable that it might have some other advantage. Anyway here it is an
example of it,
template <class T, class>
struct type_fwd
{
typedef T type;
};
template <class T, class = T>
struct has_key_type
{
enum { value = false };
};
template <class T>
struct has_key_type
<T, typename type_fwd<T, typename T::key_type>::type>
{
enum { value = true };
};
struct foo { struct key_type; };
struct bar { };
char x[ has_key_type< foo >::value ];
char y[ !has_key_type< foo>::value ];
which compiles fine in Comeau 4.3 BETA#2. I've posted a few other examples
of using this technique here before, but I've never managed to get people
very interested in them ;-(.
http://groups.google.com/groups?q=richard%40ex-parrot.com+type_fwd&hl=en&ie=
ISO-8859-1&oe=ISO-8859-1&selm=1008936696.14508.0.nnrp-10.3e31d362%40news.dem
on.co.uk&rnum=3
http://groups.google.com/groups?q=richard%40ex-parrot.com+type_fwd&hl=en&ie=
ISO-8859-1&oe=ISO-8859-1&selm=1011126576.17796.0.nnrp-02.3e31d362%40news.dem
on.co.uk&rnum=1
http://groups.google.com/groups?q=richard%40ex-parrot.com+type_fwd&hl=en&ie=
ISO-8859-1&oe=ISO-8859-1&selm=1015972118.3770.0.nnrp-12.3e31d362%40news.demo
n.co.uk&rnum=2
--
Richard Smith
5.3.3/2 says "When [sizeof is] applied to a reference or a reference type,
the result is the size of the referenced type." So sizeof( char(&)[N] ) ==
sizeof( char[N] ) (you couldn't have used char[N] as the type because the
return value of a function may not be an array type). 5.3.3/2 later says
"When applied to an array, the result is the total number of bytes in the
array. This implies that the size of an array of n elements is n times the
size of an element. So sizeof( char(&)[N] ) == N*sizeof(char), and so
clearly sizeof(char (&)[N]) and sizeof(char (&)[M]) are equal if and only if
N == M.
--
Richard Smith
I also think that it would be nice. Plus, I think that we could get some
better
error messages out of it than current STL-type messages, because types can
make
assertions about other types. If they fail, you have a built-in error
message.
I think it should be legal, just because it would expand the range of what
meta-programming can do.
Paul Mensonides
Oh, I misunderstood, I'm not using sizeof(sizer<1>), etc. I'm using
sizeof(sizer<1>.value), which is the array, not the structure.
Paul Mensonides
[snip example]
> which compiles fine in Comeau 4.3 BETA#2. I've posted a few other examples
> of using this technique here before, but I've never managed to get people
> very interested in them ;-(.
[snip urls]
It interests me, but this is not the first time I've seen this sort of thing.
:)
Just to test something, I tried the following on both Comeau 4.2.45.2c
BETA#5 -and- the 4.3 compiler online. In both cases it failed with a compiler
assertion in 'templates.c'...
// utitities
template<class T, T V> struct map_integral {
static const T value = V;
};
template<class T, T V> const T map_integral<T, V>::value;
template<class T> struct type_to_type {
typedef T type;
};
//
template<class T, class> struct type_fwd : type_to_type<T> { };
template<class T, class = T>
struct has_key_type : map_integral<bool, false> { };
template<class T>
struct has_key_type<T, typename type_fwd<T, typename T::key_type>::type>
: map_integral<bool, true> { };
// basically, your example strongly-typed and crunched. :)
// some sample classes:
struct A { };
struct B {
struct key_type { };
};
struct C {
template<class> struct key_type { };
};
struct D {
void key_type(void) {
return;
}
}
struct E {
static const bool key_type = false;
};
const bool E::key_type;
// test run:
int main() {
std::cout
<< has_key_type<A>::value << ' '
<< has_key_type<B>::value << ' '
<< has_key_type<C>::value << ' '
<< has_key_type<D>::value << ' '
<< has_key_type<E>::value << ' '
;
endl(std::cout);
return 0;
}
This will not compile if 'struct C' is tested. I tried all sorts of ways to
get
around this and finally came up with this:
template<class> struct split;
template< template<class> class T, class T1 >
struct split< T<T1> > {
struct type { };
};
template< template<class, class> class T, class T1, class T2 >
struct split< T<T1, T2> > {
struct type { };
};
template< template<class, class, class> class T, class T1, class T2, class T3
>
struct split< T<T1, T2, T3> > {
struct type { };
};
// etc. :(
template<class T> class has_template_key_type {
private:
template<class U>
static small_t check(
typename split<
typename U::template key_type<null_t>
>::type*
);
template<class U>
static small_t check(
typename split<
typename U::template key_type<null_t, null_t>
>::type*
);
template<class U>
static small_t check(
typename split<
typename U::template key_type<null_t, null_t, null_t>
>::type*
);
// etc. :(
template<class U> static large_t check(...);
public:
static const bool value
= sizeof(check<T>(0)) == sizeof(small_t);
};
template<class T> const bool has_template_key_type<T>::value;
template<class T, bool V = has_template_key_type<T>::value>
class has_key_type {
private:
template<class U> static small_t check(typename U::key_type*);
template<class U> static large_t check(...);
public:
static const bool value
= sizeof(check<T>(0)) == sizeof(small_t);
};
template<class T, bool V> const bool has_key_type<T, V>::value;
template<class T> struct has_key_type<T, true> {
static const bool value = false;
};
template<class T> const bool has_key_type<T, true>::value;
---------------
This works! All I can say is "ouch". The repetition is not all that bad,
because I perform it with the preprocessor, but this is an obvious hack around
what seems to be a compiler error.
[ ... ]
> This will not compile if 'struct C' is tested. I tried all sorts of ways
to
> get
> around this and finally came up with this:
As you say, it sounds like a compiler bug. I'm sure if Comeau / EDG thought
it really was an error condition they would have given it a proper error
message.
--
Richard Smith