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

has_member implementation(template metaprogramming)

111 views
Skip to first unread message

ُل

unread,
Apr 3, 2005, 6:00:17 PM4/3/05
to
//Well,this one is,just like the title sais,about Template Metaprogramming.
// I am sorry,if it lready exists, but I had this idea today and tried it.
// Just follow the comments to understand.

typedef char Small;
typedef struct {char unused[2];} Big;
// or just whatever you like

// the first template parameter is the type to check,
// the second the supposed type of the member.
// Just like with checking for a nested class, just using
// the default parameter for SFINAE.Note that if there wasn't
// this second template parameter in the second function,
// then this function would always be chosen.
#define DEFINE_HAS_MEMBER_WITH_TYPE(name)\
template <typename T,typename U> \
struct has_member_##name##_with_type\
{\
private:\
/* Note that in order to disambiguate the calls I need
the first dummy parameter.
*/\
template <typename U,typename V>\
static ::Small has_##name(int,V U::* = &U::name);\
\
template <typename U,typename V>\
static ::Big has_##name(...);\
\
public:\
enum{value=sizeof(has_member_##name##_with_type::template
has_##name<T,U>(0)\
)==sizeof(::Small)};\
}
// No comma intended,just like in the following class.
// You can add one if that's your wish.

// A little bit trickier,because of the conversion.
// Now it will even say that there is a member function foo,
// even if it is the member data.On the other hand, you don't need
// to specify the type.
#define DEFINE_HAS_MEMBER(name)\
template <typename T> \
struct has_member_##name\
{\
private:\
template <typename U>\
static ::Small has_##name(int,int U::* = reinterpret_cast<int
U::*>(&U::name));\
\
template <typename U>\
static ::Big has_##name(...);\
\
public:\
enum{value=sizeof(has_member_##name::template has_##name<T>(0))\
==sizeof(::Small)};\
}


// I would like to hear the people's opinion.
//I wouldalso like to note, that English isn't my native language,
// so sorry if I did any mistake.


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

David Abrahams

unread,
Apr 4, 2005, 9:14:41 AM4/4/05
to
"ُل" <efim.slob...@arcor.de> writes:

> //Well,this one is,just like the title sais,about Template Metaprogramming.
> // I am sorry,if it lready exists, but I had this idea today and tried it.
> // Just follow the comments to understand.
>

....

> // I would like to hear the people's opinion.

Well, I don't know what compiler you tried it on, but it doesn't
compile for me on a conforming compiler (illegal use of "::template").
Even after that's fixed, though, it can't work, because SFINAE only
applies to function types and *not* to default function argument
expressions.

Comeau C/C++ 4.3.3 (Aug 6 2003 15:13:37) for ONLINE_EVALUATION_BETA1
Copyright 1988-2003 Comeau Computing. All rights reserved.
MODE:strict errors C++

"ComeauTest.c", line 55: error: class "x" has no member "z"
DEFINE_HAS_MEMBER(z);
^
detected during:
instantiation of "Small has_member_z<T>::has_z(int, int U::*)
[with T=x, U=x]" at line 55
instantiation of class "has_member_z<T> [with T=x]" at line 58

"ComeauTest.c", line 58: error: the size of an array must be greater than zero
char test2[!has_member_z<x>::value];

Oh, and a point of style: synthesizing has_##name function names is
really unnecessary complication. The name "test" would work just as
well or better.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

ُل

unread,
Apr 4, 2005, 12:12:48 PM4/4/05
to

"УА" <efim.slob...@arcor.de> сообщил/сообщила в новостях следующее:
news:4250433e$0$11461$9b4e...@newsread2.arcor-online.net...

Well,now I see you are right.I did even more mistakes,e.g. shadowing the
template parameter.
Probably the tests worked because I only tried it with a few fundamental
types and a class having
this member. Where the "SFINAE" appeared to work.Still,thanks for your help.
P.S.:Would be nice if the compiler considered the default parameters while
using SFINAE.
And I know a "test" would be OK,I just think it's nicer that way.

Vladimir Marko

unread,
Apr 4, 2005, 12:10:44 PM4/4/05
to
õá wrote:
[snip]
> #define DEFINE_HAS_MEMBER_WITH_TYPE(name)\
[snip]
> #define DEFINE_HAS_MEMBER(name)\
[snip]

Aside from the problems pointed out by David Abrahams...
There's been a number of threads about compile-time checking
for members lately. Just check out threads like "compile-time
discrimination of types" or "detecting existence of a member
function (SFINAE ?)" in clc++m. So, I'll once again note that
the most important problem is the open core isuue #339:
http://www.open-std.org/jtc1/­sc22/WG21/docs/cwg_active.html­#339

Regards,
Vladimir Marko

ُل

unread,
Apr 5, 2005, 3:37:59 AM4/5/05
to

"David Abrahams" <da...@boost-consulting.com> сообщил/сообщила в новостях
следующее: news:uwtrj2...@boost-consulting.com...

So this also wouldn't work?

// the same stuff as above

#define DEFINE_HAS_MEMBER(name)\
template <typename T> \
struct has_member_##name\
{\
private:\
\
template <typename U>\

static char (&select_test_func(const U&))[1];\
\
template <typename U>\
static ::Small test(Selecter<sizeof(select_test_func(&U::name))>*);\
\
template <typename U>\
static ::Big test(...);\
\
public:\
enum{value=sizeof(test<T>(0))==sizeof(::Small)};\
}

I tried this and my compiler told me:
> In instantiation of `has_member_clone<HasClone>':
> 151 D:\Leo\C++\DevC++\temp.cpp instantiated from here
> 151 D:\Leo\C++\DevC++\temp.cpp call_expr cannot be mangled due to a defect
in the C++ ABI

My compiler is g++ 3.4.2 .Now what on earth does this mean?

Pete Becker

unread,
Apr 5, 2005, 3:39:34 AM4/5/05
to
őá wrote:

> P.S.:Would be nice if the compiler considered the default parameters while
> using SFINAE.

Well, maybe. On the other hand, instead of relying so heavily on
now-you-see-it-now-you-don't, it might be easier to have direct language
support for this sort of query.

--

Pete Becker
Dinkumware, Ltd. (http://www.dinkumware.com)

ُل

unread,
Apr 6, 2005, 3:29:52 AM4/6/05
to
So,now I got it.Halleluia!Sorry that I forgot to include the
Selector class in my last message, but I thought that it's
pretty obvious.
Try this one and see if it works. :-)

template <int> Selector;
____________________________


#define DEFINE_HAS_MEMBER(name)\
template <typename T> \
struct has_member_##name\
{\
private:\
\
template <typename U>\

static ::Small test(Selecter<sizeof(&U::name)>*);\
\
template <typename U>\


static ::Big test(...);\
\
public:\
enum{value=sizeof(test<T>(0))==sizeof(::Small)};\
}

____________________________
It can be no simpler and it really works.
The only problem is that there is still
acompilation error if the member is
inaccessible(i.e. nonpublic).But otherwise
value is just either 0 or 1.

I tried it with the following types:


struct HasClone
{
void clone(){}
};

struct NoClone
{};

and of course the ol' good int.
DEFINE_HAS_MEMBER(clone);

has_member_clone<HasClone>::value==1;
has_member_clone<NoClone>::value==0;
has_member_clone<int>::value==0;


All of those of course true!!!!
It still would be easier and safer if
the language would support it
without the need to write overly complicated
templates.I hope C++0x will do this and much more
(justa joke).

What remains is checking for a member of a certain type.
P.S.: I hope my compiler is standard-comforming enough.
Thanks for replies!

Alberto Barbati

unread,
Apr 6, 2005, 3:33:25 AM4/6/05
to
Pete Becker wrote:
> őá wrote:
>
>>P.S.:Would be nice if the compiler considered the default parameters while
>>using SFINAE.
>
> Well, maybe. On the other hand, instead of relying so heavily on
> now-you-see-it-now-you-don't, it might be easier to have direct language
> support for this sort of query.
>

Are there proposals about that? Looks an interesting idea.

I know that VC7.1 has two non-standard extension keywords named
__if_exists/__if_not_exists, that work as follow (example taken from MS
documentation):

template<typename T>
class X : public T {
public:
void Dump() {
std::cout << "In X<T>::Dump()" << std::endl;

__if_exists(T::Dump) {
T::Dump();
}

__if_not_exists(T::Dump) {
std::cout << "T::Dump does not exist" << std::endl;
}
}
};

How is this approach rated by the Gurus? Personally it makes me a bit
nervous, because it looks like a statement but behaves like an
#if/#endif, but I have to admit that it does the job quite niflty and
doesn't look too difficult to implement.

Alberto

ُل

unread,
Apr 6, 2005, 3:34:06 AM4/6/05
to

Well, I have still found only two possible
implementations for the "DEFINE_HAS_MEMBER_WITH_TYPE"
thing.Neither are truly good.

1)IMHO it shouldn't be considered ,because the information
would only be availible at run-time.But there would be still
almost no performance penalty(it isn't typeid :P ).


The idea is using this functions:

template <typename T,typename U>
bool isSame(U& x)
{
return is_same<T,U>::value;
}

#define DEFINE_HAS_MEMBER_WITH_TYPE(name)\
template <typename T,typename U>\

struct name##_checker\
{\
static bool value=isSame<U>(&T::name);\
};\


\
template <typename T,typename U> \
struct has_member_##name##_with_type\
{\

public:\
enum{value=has_member_##name<T>::value\
&& name##_checker<T,U>::value};\
}

Just as I said,pretty much useless.

2)

#define DEFINE_HAS_MEMBER_WITH_TYPE(name)\
\
template <typename T,typename U>\

struct name##_checker\
{\
enum{value=is_same<typeof(&T::name),U>::value};\
};\


\
template <typename T,typename U> \
struct has_member_##name##_with_type\
{\

public:\
enum{value=if_<has_member_##name<T>,\
name##_checker<T,U>,int_<0> >::type::value};\
}

value is now a compile-time constant,but the cost...
The cost is the portability.Bill Gibbons idea for typeof
is very good, but it could be that some user-defined
type X wasn't registered.So I have to rely on some
future extension.


Another note:both require that has_member_##name is defined.
One can duplicate the code to eliminate dependencies.I hope
s.o. can find a better solution.

Pete Becker

unread,
Apr 6, 2005, 4:41:33 PM4/6/05
to
Alberto Barbati wrote:

> Pete Becker wrote:
>
>>őá wrote:
>>
>>
>>>P.S.:Would be nice if the compiler considered the default parameters while
>>>using SFINAE.
>>
>>Well, maybe. On the other hand, instead of relying so heavily on
>>now-you-see-it-now-you-don't, it might be easier to have direct language
>>support for this sort of query.
>>
>
>
> Are there proposals about that? Looks an interesting idea.

Not that I know of. I've been waiting for an opportunity to take that
potshot at SFINAE, and this was it. <g> I think it's a really ugly hack.

>
> I know that VC7.1 has two non-standard extension keywords named
> __if_exists/__if_not_exists, that work as follow (example taken from MS
> documentation):
>
> template<typename T>
> class X : public T {
> public:
> void Dump() {
> std::cout << "In X<T>::Dump()" << std::endl;
>
> __if_exists(T::Dump) {
> T::Dump();
> }
>
> __if_not_exists(T::Dump) {
> std::cout << "T::Dump does not exist" << std::endl;
> }
> }
> };
>
> How is this approach rated by the Gurus? Personally it makes me a bit
> nervous, because it looks like a statement but behaves like an
> #if/#endif, but I have to admit that it does the job quite niflty and
> doesn't look too difficult to implement.
>

There's a fair amount of discussion between library implementors and
compiler writers about how to avoid the huge compile-time overhead
imposed by some template metaprogramming techniques. None of it is
refined enough yet for public consumption.

--

Pete Becker
Dinkumware, Ltd. (http://www.dinkumware.com)

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

Gabriel Dos Reis

unread,
Apr 6, 2005, 4:48:23 PM4/6/05
to
Alberto Barbati <Alberto...@libero.it> writes:

| Pete Becker wrote:
| > őá wrote:
| >
| >>P.S.:Would be nice if the compiler considered the default parameters while
| >>using SFINAE.
| >
| > Well, maybe. On the other hand, instead of relying so heavily on
| > now-you-see-it-now-you-don't, it might be easier to have direct language
| > support for this sort of query.
| >
|
| Are there proposals about that? Looks an interesting idea.

When and if we have "concepts", they should be able to express those notions.

| I know that VC7.1 has two non-standard extension keywords named
| __if_exists/__if_not_exists, that work as follow (example taken from MS
| documentation):
|
| template<typename T>
| class X : public T {
| public:
| void Dump() {
| std::cout << "In X<T>::Dump()" << std::endl;
|
| __if_exists(T::Dump) {
| T::Dump();
| }
|
| __if_not_exists(T::Dump) {
| std::cout << "T::Dump does not exist" << std::endl;
| }
| }
| };
|
| How is this approach rated by the Gurus?

I'm not a Guru but I believe it is far too specialized and I rather
have it in a way I can express through the type system (i.e. overload).
(The last sentence does not mean I'm a now-you-see-it-now-you-don't
fanatic)

| Personally it makes me a bit
| nervous, because it looks like a statement but behaves like an
| #if/#endif,

Yeah, just trade some hashes for more underbars ;-)

--
Gabriel Dos Reis
g...@integrable-solutions.net

Vladimir Marko

unread,
Apr 6, 2005, 5:41:37 PM4/6/05
to
őá wrote:
> Well, I have still found only two possible
> implementations for the "DEFINE_HAS_MEMBER_WITH_TYPE"
> thing.Neither are truly good.
>
> 1)IMHO it shouldn't be considered ,because the information
> would only be availible at run-time.But there would be still
> almost no performance penalty(it isn't typeid :P ).
[snip]

> 2)
>
> #define DEFINE_HAS_MEMBER_WITH_TYPE(name)\
> \
> template <typename T,typename U>\
> struct name##_checker\
> {\
> enum{value=is_same<typeof(&T::name),U>::value};\
> };\

[snip]


> value is now a compile-time constant,but the cost...
> The cost is the portability.Bill Gibbons idea for typeof
> is very good, but it could be that some user-defined
> type X wasn't registered.So I have to rely on some
> future extension.
>
>
> Another note:both require that has_member_##name is defined.
> One can duplicate the code to eliminate dependencies.I hope
> s.o. can find a better solution.

I thought I gave you all references you need. The threads
I wrote about give quite good solutions and do not rely on
"typeof". And pretty much anything has a portability problem
because the implementers make different decisions about the
open core issue #339 (such as gcc issuing that "call_expr
can not be mangled..." for anything non-trivial). For your
convenience here are some nice solutions working under gcc
3.4.2 (without macros, just for a member named "foo"):

#include <iostream>
#include <ostream>

struct no { };
struct yes { no no_[2]; };

// check for unambigous public member foo of any type
template <typename T>
struct has_foo{
template <int>
struct yes_holder { typedef yes type; };

template<typename>
static no test(...);

template <typename U>
static typename yes_holder<sizeof(&U::foo)>::type test(U*);

static const bool value= sizeof(yes)==sizeof(test<T>((T*)0));
};

// check for an unambigous public member foo of specified type
template <typename fooType,typename T>
struct has_typed_foo{
template <fooType>
struct yes_holder { typedef yes type; };

template<typename>
static no test(...);

template <typename U>
static typename yes_holder<&U::foo>::type test(U*);

static const bool value= sizeof(yes)==sizeof(test<T>((T*)0));
};

// usage examples -- classes
struct A { };
struct B { int foo; };
struct C : B { };
struct D {
void foo() { }
static void foo(int) { }
};

int main(){
// usage examples -- check for member foo of any type
std::cout << has_foo<A>::value << std::endl; //false
std::cout << has_foo<B>::value << std::endl; // true
std::cout << has_foo<C>::value << std::endl; // true
// std::cout << has_foo<D>::value << std::endl; // ambiguous

// usage examples -- check for member foo with specified type
std::cout << has_typed_foo<void*,B>::value << std::endl; // false
std::cout << has_typed_foo<int B::*,B>::value << std::endl; // true
std::cout << has_typed_foo<int B::*,C>::value << std::endl; // true
std::cout << has_typed_foo<int C::*,C>::value << std::endl; // false
std::cout << has_typed_foo<void (D::*)(),D>::value << std::endl; //
true
std::cout << has_typed_foo<void (*)(int),D>::value << std::endl; //
true
return 0;
}

Private/protected and ambiguous members are a huge problem
that has been raised in a new thread "SFINAE HasMethod Traits
crashes on private methods". I was making a few tests with
gcc and I'm going to write a follow-up soon, so stay tuned.

Best,
Vladimir Marko

David Abrahams

unread,
Apr 7, 2005, 3:26:27 AM4/7/05
to
Gabriel Dos Reis <g...@integrable-solutions.net> writes:

> Alberto Barbati <Alberto...@libero.it> writes:
>
> | Pete Becker wrote:
> | > őá wrote:
> | >
> | >>P.S.:Would be nice if the compiler considered the default parameters while
> | >>using SFINAE.
> | >
> | > Well, maybe. On the other hand, instead of relying so heavily on
> | > now-you-see-it-now-you-don't, it might be easier to have direct language
> | > support for this sort of query.
> | >
> |
> | Are there proposals about that? Looks an interesting idea.
>
> When and if we have "concepts", they should be able to express those
> notions.

Maybe as requirements, but I doubt they will work for introspection.
IMO explicit conformance declarations are necessary.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

ZMS

unread,
Apr 7, 2005, 3:26:00 AM4/7/05
to
1. The source from 'ُل' or "Vladimir Marko"
does not compile in MSVC7.1 (MSC1301)
resulting 'INTERNAL COMPILER ERROR".
Is there any workaround in that compiler
( except using __if_exists and __if_not_exists ) ?

2. Testing of a template member function like following,
template< typename Stream >
void Output( Stream& stream );
causes compilation error in both sources.
But __if_exists implementaion in MSVC like following,


template< typename T >
struct has_foo {

__if_exists( T::foo ) { static const bool value = true; }
__if_not_exists( T::foo ) { static const bool value = false; }
};
works fine.
Is there any workaround on this case in other compilers ?

Vladimir Marko

unread,
Apr 7, 2005, 10:52:39 AM4/7/05
to
ZMS wrote:
> 1. The source from 'õá' or "Vladimir Marko"

> does not compile in MSVC7.1 (MSC1301)
> resulting 'INTERNAL COMPILER ERROR".
> Is there any workaround in that compiler
> ( except using __if_exists and __if_not_exists ) ?

I don't have MSVC7.1, only the free "Microsoft Visual C++
Toolkit 2003" and a quick check revealed that has_typed_foo
works fine, but has_foo causes an ICE on instantiation.
As I wrote before, the code is not portable due to different
approaches implementers choose wrt. the core issue #339
(ICE may be an indication that this issue was not considered
at all in this version of compiler).

> 2. Testing of a template member function like following,
> template< typename Stream >
> void Output( Stream& stream );
> causes compilation error in both sources.
> But __if_exists implementaion in MSVC like following,
> template< typename T >
> struct has_foo {
> __if_exists( T::foo ) { static const bool value = true; }
> __if_not_exists( T::foo ) { static const bool value = false; }
> };
> works fine.
> Is there any workaround on this case in other compilers ?

Well, If we add the class

struct E{
template <typename T>
void foo(T*) { }
};

the statements

std::cout << has_typed_foo<void (E::*)(int),E>::value << std::endl;
// false
std::cout << has_typed_foo<void (E::*)(int*),E>::value << std::endl;
// true

work as expected with both gcc 3.4.2 and MSVC++ Toolkit 2003.
Since &E::foo is a(n infinite) set of functions gcc correctly
reports ambiguity when trying to test has_foo<E>::value. The
fact that MS extension __if_exists doesn't care for ambiguity
clearly demonstrates that it's something completely different.

Regards,
Vladimir Marko

Gabriel Dos Reis

unread,
Apr 7, 2005, 11:00:19 AM4/7/05
to
David Abrahams <da...@boost-consulting.com> writes:

| Gabriel Dos Reis <g...@integrable-solutions.net> writes:
|
| > Alberto Barbati <Alberto...@libero.it> writes:
| >
| > | Pete Becker wrote:
| > | > őá wrote:
| > | >
| > | >>P.S.:Would be nice if the compiler considered the default parameters while
| > | >>using SFINAE.
| > | >
| > | > Well, maybe. On the other hand, instead of relying so heavily on
| > | > now-you-see-it-now-you-don't, it might be easier to have direct language
| > | > support for this sort of query.
| > | >
| > |
| > | Are there proposals about that? Looks an interesting idea.
| >
| > When and if we have "concepts", they should be able to express those
| > notions.
|
| Maybe as requirements, but I doubt they will work for introspection.
| IMO explicit conformance declarations are necessary.

Please, could you articulate the connection with has_member as posted
in this thread and message I was responding to?

--
Gabriel Dos Reis
g...@integrable-solutions.net

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

David Abrahams

unread,
Apr 8, 2005, 3:39:21 AM4/8/05
to
Gabriel Dos Reis <g...@integrable-solutions.net> writes:

> David Abrahams <da...@boost-consulting.com> writes:
>
> | Gabriel Dos Reis <g...@integrable-solutions.net> writes:
> |
> | > Alberto Barbati <Alberto...@libero.it> writes:
> | >
> | > | Pete Becker wrote:
> | > | > őá wrote:
> | > | >
> | > | >>P.S.:Would be nice if the compiler considered the default parameters while
> | > | >>using SFINAE.
> | > | >
> | > | > Well, maybe. On the other hand, instead of relying so heavily on
> | > | > now-you-see-it-now-you-don't, it might be easier to have direct language
> | > | > support for this sort of query.
> | > | >
> | > |
> | > | Are there proposals about that? Looks an interesting idea.
> | >
> | > When and if we have "concepts", they should be able to express those
> | > notions.
> |
> | Maybe as requirements, but I doubt they will work for introspection.
> | IMO explicit conformance declarations are necessary.
>
> Please, could you articulate the connection with has_member as posted
> in this thread and message I was responding to?

I'll try. I understood you to be saying that in-language concept
support would amount to "direct language support for this sort of
query," i.e. that you'd be able to:

1. Declare a concept X that captures the notion "models of this
concept must have member foo"

2. Check that an arbitrary type Y conforms to concept X without
causing a compilation error, e.g. by using concept-based
overload resolution or perhaps even a more direct expression of
the idea.

I happen to believe that any workable system for concepts in C++ will
not be doing syntactic conformance detection, but instead will rely on
explicit declarations of a type's (or multiple types') conformance to
a concept. My conclusion is that there will be no incidental support
for introspecting about whether a type has a member foo. We might
design that in as an extension, but the capability doesn't fall
naturally out of the addition of concept support.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

Gabriel Dos Reis

unread,
Apr 8, 2005, 10:30:58 AM4/8/05
to
David Abrahams <da...@boost-consulting.com> writes:

I guess what I'm saying is this: any design of concept that does not
improve over or render SFINAE trick redundant is close to useless.
Since one of reasons why SFINAE is effective, for a category of
programmers, is precisely that they do not have to go through explicit
formal security screening (e.g. they don't have to explicitly say to
the compiler that they conform to the SFINAE trick "requirements") it
appears to me that any usable design of concepts must allow for such
implicit deduction/selection.

| I happen to believe that any workable system for concepts in C++ will
| not be doing syntactic conformance detection, but instead will rely on
| explicit declarations of a type's (or multiple types') conformance to
| a concept. My conclusion is that there will be no incidental support
| for introspecting about whether a type has a member foo. We might
| design that in as an extension, but the capability doesn't fall
| naturally out of the addition of concept support.

I do not know what you mean by "introspection" (it is a term whose
exact meaning seems to vary from people to people), and therefore I
will not claim that any design of concept must support such thing
(nor claim the contrary).

However, for the specific case at hand in this thread, I do claim and
maintain that any concept system we have for C++ should handle that
case, because any such system should make SFINAE trick redundant and
let programmers express directly and simply what they mean without
relying on language subtleties.

I do understand why a template author may get very paranoid about
his/her template and require every user of his/her product to
explicitly show patte blanche, but I do NOT think that every use of
templates falls in that category.
Consequently, "explicit conformance statement", if it exists, must be
optional.

Given that, I can declare a concept that requires presence of member
foo, and overload functions on that concept -- your points 1. and 2.

--
Gabriel Dos Reis
g...@cs.tamu.edu
Texas A&M University -- Department of Computer Science
301, Bright Building -- College Station, TX 77843-3112

dave_abrahams

unread,
Apr 11, 2005, 3:09:16 AM4/11/05
to
> | I'll try. I understood you to be saying that in-language concept
> | support would amount to "direct language support for this sort of
> | query," i.e. that you'd be able to:
> |
> | 1. Declare a concept X that captures the notion "models of this
> | concept must have member foo"
> |
> | 2. Check that an arbitrary type Y conforms to concept X without
> | causing a compilation error, e.g. by using concept-based
> | overload resolution or perhaps even a more direct expression
of
> | the idea.
>
> I guess what I'm saying is this: any design of concept that does not
> improve over or render SFINAE trick redundant is close to useless.

No offense intended, but it's hard to take any argument based on that
premise seriously. The most successful and widely-used example of a
Generic Programming system (STL) has no use for "the SFINAE trick."
(**) That is also true of the Boost Graph Library, which is probably
the most-evolved example of Generic Programming I've ever seen.

In fact, the STL relies on _explicit_ declarations of a type's
conformance to a concept: that's just what the iterator_category
member of iterator_traits is. The author of an iterator type is
expected (either through specialization of iterator_traits or by
allowing its default implementation to look up an iterator_category
member in the iterator itself) to tell the library that her type is an
iterator and just what kind of iterator it is.

> Since one of reasons why SFINAE is effective, for a category of
> programmers, is precisely that they do not have to go through
explicit
> formal security screening (e.g. they don't have to explicitly say to
> the compiler that they conform to the SFINAE trick "requirements") it
> appears to me that any usable design of concepts must allow for such
> implicit deduction/selection.

(**) Just to be clear about what we mean: in the context of this
discussion "the SFINAE trick" means using SFINAE to make
determinations about the structural properties of a type X, namely, to
ask "does X have member m?" at compile-time:

some_template< has_m<X>::value >::whatever
^^^^^^^^^^^^^^^

I do not include the use of SFINAE to discriminate between or remove
members of an overload set (typical examples at the end of this
message for those reading along) to be "the SFINAE trick" simply
because it is uncontroversial that language-based concept support must
take care of those cases.

The uses of SFINAE for structural conformance detection are
exceedingly rare in the code I've seen. When I've used SFINAE for
that purpose, it has always felt a little dirty, because it usually
doesn't actually detect conformance to a concept -- it's just a pretty
good guess. Concepts have semantic requirements that are beyond the
compile-time machinery's ability to distinguish. For example, a type
that is EqualityComparable doesn't just have an operator== and
operator!= (structural properties). There is a special semantic
relationship between these operators: they must be inverses of one
another.

For that reason, SFINAE for structural conformance detection is nearly
always packaged inside a trait/metafunction with the understanding
that if the system guesses wrong about the meaning of that structure,
the user can specialize the trait to correct it.

Moreover, that use of SFINAE is arguably a very bad idea. Consider
what goes in the documentation of a generic component that uses the
SFINAE trick. You generally can't write "does _this_ if the component
conforms to concept Y and otherwise it does _that_," because your
detector only tests some of the structural aspects of concept Y.
Instead you end up having to enumerate those structural aspects you
are detecting, and the same would hold true for a system with language
support for concepts. The inability to simply name the concept makes
for complicated and confusing documentation, which is a strong
indicator that there's a design problem somewhere.

> | I happen to believe that any workable system for concepts in C++
will
> | not be doing syntactic conformance detection, but instead will rely
on
> | explicit declarations of a type's (or multiple types') conformance
to
> | a concept. My conclusion is that there will be no incidental
support
> | for introspecting about whether a type has a member foo. We might
> | design that in as an extension, but the capability doesn't fall
> | naturally out of the addition of concept support
>

> I do not know what you mean by "introspection"

I mean the ability to determine structural properties of a type
without generating a compile-time error.

<snip>

> However, for the specific case at hand in this thread, I do claim and
> maintain that any concept system we have for C++ should handle that
> case, because any such system should make SFINAE trick redundant and
> let programmers express directly and simply what they mean without
> relying on language subtleties.

I agree that language support for concepts should "let programmers


express directly and simply what they mean without relying on language

subtleties," but disagree strongly that making the SFINAE trick
redundant should be a goal that drives the design of this feature.
The SFINAE trick as I've described it:

a. Has little or nothing to do with Generic Programming

b. Is used too rarely to be allowed to change the shape of such an
important language feature.

> I do understand why a template author may get very paranoid about
> his/her template and require every user of his/her product to
> explicitly show patte blanche, but I do NOT think that every use of
> templates falls in that category.

Not every use of templates today can or should be addressed by the
introduction of a concepts feature. Concepts in the language should
specifically address the problems of Generic Programming, because that
is the domain for which the concepts we've been using without language
support were invented. The problems of template metaprogramming,
generative programming, compile-time introspection, etc. should be
left to other mechanisms unless concepts are a natural fit. In this
case, they aren't.

Here are some examples of "non-SFINAE trick" SFINAE which everyone
agrees *ought* to be obsoleted by concept support. Concepts ought to
be able to prevent functions from matching (as in the case of sort
below when a Forward Iterator is passed) or dispatch to appropriate
implementations for various input types (as in the case of choosing
separate implementations of binary_search for Bidirectional Iterators
and Random Access Iterators).

========

Removing inappropriate overloads from an overload set in a library's
user interface:

// sort only works on Bidirectional Iterators
typename boost::enable_if<
boost::is_convertible<
typename std::iterator_traits<
BidirectionalIterator
>::iterator_category
, std::bidirectional_iterator_tag
>
>::type sort(BidirectionalIterator, BidirectionalIterator);

// binary_search only works on Bidirectional Iterators
typename boost::enable_if<
boost::mpl::and_<

boost::is_convertible<
typename std::iterator_traits<
BidirectionalIterator
>::iterator_category
, std::bidirectional_iterator_tag
>

// We have a different implementation for
// Random Access Iterators
, boost::mpl::not_<
boost::is_convertible<
typename std::iterator_traits<
BidirectionalIterator
>::iterator_category
, std::random_access_iterator_tag
>
>
, BidirectionalIterator

>
>::type binary_search(BidirectionalIterator, BidirectionalIterator);

// There is a faster implementation of binary_search for
// Random Access Iterators
typename boost::enable_if<
boost::is_convertible<
typename std::iterator_traits<
RandomAccessIterator
>::iterator_category
, std::random_access_iterator_tag
>
, RandomAccessIterator
>::type binary_search(RandomAccessIterator, RandomAccessIterator);

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

Gabriel Dos Reis

unread,
Apr 12, 2005, 2:52:09 AM4/12/05
to
"dave_abrahams" <da...@boost-consulting.com> writes:

| > | I'll try. I understood you to be saying that in-language concept
| > | support would amount to "direct language support for this sort of
| > | query," i.e. that you'd be able to:
| > |
| > | 1. Declare a concept X that captures the notion "models of this
| > | concept must have member foo"
| > |
| > | 2. Check that an arbitrary type Y conforms to concept X without
| > | causing a compilation error, e.g. by using concept-based
| > | overload resolution or perhaps even a more direct expression
| of
| > | the idea.
| >
| > I guess what I'm saying is this: any design of concept that does not
| > improve over or render SFINAE trick redundant is close to useless.
|
| No offense intended, but it's hard to take any argument based on that
| premise seriously.

Well, it appears to me that you just did; but maybe that is just a
matter of differences in perception. Anyway, thanks for the
magnanimous credit, it overflowed my expectations.

| The most successful and widely-used example of a
| Generic Programming system (STL) has no use for "the SFINAE trick."

STL is a successful example of generic programming, as implemented by
C++, but I disagree with your assertion that it relies on explicit
conformance to a concept. I'll develop the reasons below, and I will
refrain from quoting the rest of your message because what I'd say will
just be answers to repetitions of the same fundamental idea.

It is unfortunate that you have decided to call "structural
conformance" what I carefully labeled as optional explicit
conformance. I do know whether that is a sign of a profound
misunderstanding what I mean by

"explicit conformance statement", if it exists, must be optional.

or a rhetorical tactic that further confuses the discussion.
(That is not a rhetorical tactic, I really do not know.)
In my book, not having compulsory explicit conformance statement !=
making structural conformance compulsory.


Let me go through some aspects of the line of thinking behind these
ideas of explicit vs. implicit matching and its impact on actual
programming as opposed to abstract language design.

One of the earlier incarnations of STL was written in Ada, but was not
as successful as the C++ incarnation is. According to the author, C++
would be the first language that let him express what he wanted to
express. I credit part of issue to the Ada's requirement of explicit
instantiation before use. Explicit instantiation before use is nice
in theory, but it has serious mismatch when it comes to techniques
that scale. That is not to say it is a bad idea in absolute: only
making it compulsory is, IMO (I can definitely conceive of
situations where a generic function author would require explicit
patte blanche from user before touching, and as a matter of fact, in
the concept design I happen to co-author there are such examples and
description of behaviour).

You mentioned std::iterator<>::iterator_category as a proof that STL
relies on explicit conformance to a concept. However, the traits
std::iterator<> is a tiny part (I'm tempted to say a "ghetto") of STL.
As I said earlier, STL is successful because it relies on a key
ingredient that consists of having the compiler apply knowledge it has
from the code -- instead of requiring the user to tell it what it
already knows. When I write

double Abs(double x) { return x < 0 ? -x : x; }

double ary[] = {-49, -43.4, .79, -6..94 };
std::transform(ary, ary + 4, ary, Abs);

I do not have to explicitly tell the STL component that that my Abs()
function is unary, can accept a double and produce a double. Those
information are already contained in the declaration. And it works
great. STL does not rely on me telling it that Abs() is a
unary_function_from_double_to_double.
However, what I would like to see is a way to tell std::transform() to
do some "sanity" checks on its arguments -- without requiring
excessive verbiage from user.

Similarly, std::stack<> and other container adaptors do not require me
explicitly telling them that my underlying container has back(),
push_back() and pop_back() before use.
However, I would like to see a mechanism to tell std::stack to do some
sanity checks, without requiring excessive verbiage from user.

I guess, I could go through the STL components and exhibit one by one
examples that defeat your claim.

Concepts for C++ ought to go beyond the iterator theory. It ought to
support other styles and manifestly sound techniques.

From my perspective, the real point of the iterator theory is not
that we can do *, ++, -- or iterator<T>::iterator_category. The real
point is that of classification of algorithms based on "minimal
requirements", that turns classical problem of

number of data structures =D7 number of algorithms

into a (quasi-)linear problem.

Before closing this unusually lengthy message, let me give an example of
a technique that a compulsory explicit conformance makes exceedingly
complicated or downright outlawed: effective overloading based on
concepts.

Overloading usually assumes a set of candidates, out which only one
will be selected. In the compulsory explicit conformance scenario, as a
template library author I cannot effectively use helper functions (in
form of overloads) in my implementations. The only way I could
possibly get away will be to require users to make explicit
conformance statements to my own implementation detail concepts used
in my implementation internal functions. And, doing so require me
exposing the implementation details in the documentation.
I do not call that abstraction, nor modularity.
Furthermore, since overloads include candidates that will fail,
neither me, nor the user has any clue about what concepts the explicit
conformance statement should mention.

I have no use of a concept system that outlaws concept-based overloading,
nor do I see how it effectively supports generic programming and carry
it forward and to mainstream, beyond folklore trickeries.

--
Gabriel Dos Reis
g...@integrable-solutions.net

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

wka...@yahoo.com

unread,
Apr 13, 2005, 3:56:08 PM4/13/05
to

A possible split-the-difference compromise here is some kind of
"meta-try" construct. On possible syntax:

<? [token-sequence1] ?- [token-sequence2]
?- [token-sequence3] ... ?>

The first token sequence that does not cause a compilation error
(within the class or function) is the sequence used.

Using this construct, your example becomes:

template<typename T>
class X : public T {
public:
void Dump() {
std::cout << "In X<T>::Dump()" << std::endl;

<? T::Dump();
?- std::cout << "T::Dump() not valid" << std::endl;
?>
}
};

The big issue for me with the VC++ approach is that it is just
the beginning of a big ugly list of "meta-predicates" corresponding
to the possible usages of scoped identifiers. SFINAE and
this meta-try approach avoid this, because the desired usage of
identifier(s) are given simply by using them.

This sort of meta-try makes it more explicit than SFINAE what is
optional and what isn't. The meta-try is also more granular. I
would guess that the meta-try is easier to implement than SFINAE,
but I don't know enough about compiler implementation for my
opinion to be worth much.

It may be possible allow the usage of the meta-try outside of
template defintions, so it could be used for preprocessor
metaprogramming as well.

dave_abrahams

unread,
Apr 13, 2005, 3:57:51 PM4/13/05
to
Gabriel Dos Reis wrote:


>> STL is a successful example of generic programming, as implemented
>> by C++, but I disagree with your assertion that it relies on
>> explicit conformance to a concept.


It seems to me that if you accept these two points:

1. STL relies on iterator_traits<T>::iterator_category

2. providing iterator_traits<T>::iterator_category amounts to an
explicit declaration of conformance to a concept.

then you must accept that STL "relies on explicit conformance to a
concept." So I presume you disagree with either 1 or 2 (or both).
Which is it, please?

Parts of STL do rely on structural conformance only, but all uses of
concept based overloading/dispatching rely on this explicit declaration
of conformance.

>> I'll develop the reasons below, and I will refrain from quoting the
>> rest of your message because what I'd say will just be answers to
>> repetitions of the same fundamental idea.
>>
>> It is unfortunate that you have decided to call "structural
>> conformance" what I carefully labeled as optional explicit
>> conformance. I do know whether that is a sign of a profound
>> misunderstanding what I mean by
>>
>> "explicit conformance statement", if it exists, must be optional.
>>
>> or a rhetorical tactic that further confuses the discussion.
>> (That is not a rhetorical tactic, I really do not know.)


If you intended your quoted statement above to establish terminology,
then I misunderstood what you meant -- and, I think, quite reasonably
so. However, I might have used the term "structural conformance"
anyway because it accurately describes the default case that lies at
the core of "optional explicit conformance" as I understand it.


>> In my book, not having compulsory explicit conformance statement !=
>> making structural conformance compulsory.


I agree. However, it means that structural conformance checking is
what you get unless you go out of your way to avoid it.


>> Let me go through some aspects of the line of thinking behind these
>> ideas of explicit vs. implicit matching and its impact on actual
>> programming as opposed to abstract language design.


Now we can switch places: I don't know if that statement was a
rhetorical tactic or not. It seems to imply that I have only done
"abstract language design" and have not explored the impact on actual
programming. That implication would be at variance with fact.


>> One of the earlier incarnations of STL was written in Ada, but was
not
>> as successful as the C++ incarnation is. According to the author,
C++
>> would be the first language that let him express what he wanted to
>> express. I credit part of issue to the Ada's requirement of
explicit
>> instantiation before use. Explicit instantiation before use is nice
>> in theory,


What theory? It doesn't seem nice to me.

>> but it has serious mismatch when it comes to techniques
>> that scale. That is not to say it is a bad idea in absolute: only
>> making it compulsory is, IMO (I can definitely conceive of
>> situations where a generic function author would require explicit
>> patte blanche from user before touching, and as a matter of fact, in
>> the concept design I happen to co-author there are such examples and
>> description of behaviour).


Anyway, explicit model declarations as proposed in
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1758.pdf are
not explicit instantiations. To begin with (just so everyone reading
understands), they are completely different things with different
purposes.

Furthermore, Ada explicit instantiations proliferate for each concrete
type used as an argument to a generic function call, which of course
introduces a terrible scalability problem. Explicit model
declarations are limited to describing the concepts satisfied by types
or class templates, which has a much smaller impact.

Finally, requiring explicit declarations often has a positive impact
on scalability. I've often heard whinging from Python programmers
about the inconvenience of explicit type declarations in C++.
However, C++ scales much better than Python specifically _because_ of
those solid, well-defined types at interface boundaries. (**) This
line of thinking should be especially appealing to anyone who thinks
of concepts as "types of types."


>> You mentioned std::iterator<>::iterator_category as a proof that STL
>> relies on explicit conformance to a concept.
>> However, the traits std::iterator<> is a tiny part (I'm tempted to
>> say a "ghetto") of STL.


Wait: who mentioned std::iterator<> ? Not me. I don't see the
relevance to anything we've been discussing. I said that the standard
library's requirement that the concept modeled by an iterator match

std::iterator_traits<T>::iterator_category
^^^^^^^^^^^^^^^

is an example of relying on explicit concept conformance declarations.


>> As I said earlier, STL is successful because it relies on a
>> key ingredient that consists of having the compiler apply knowledge
>> it has from the code -- instead of requiring the user to tell it
>> what it already knows. When I write
>>
>> double Abs(double x) { return x < 0 ? -x : x; }
>>
>> double ary[] = {-49, -43.4, .79, -6..94 };
>> std::transform(ary, ary + 4, ary, Abs);
>>
>> I do not have to explicitly tell the STL component that that my
Abs()
>> function is unary, can accept a double and produce a double. Those
>> information are already contained in the declaration. And it works
>> great. STL does not rely on me telling it that Abs() is a
>> unary_function_from_double_to_double.


Actually that case would be nicely handled by a model template
provided by the standard library. So the library would contain a
concept declaration, something like this (regardless of whether or not
explicit conformance declarations are required):

template <typeid F, typeid Argument>
concept UnaryFunction
{
typename result_type;
result_type operator()(F, Argument);
};

and, with our proposal, an explicit declaration of conformance for all
function pointers of one argument would be:

template <typename R, typename P1, typename A1>
where { Convertible<A1,P1> }
model UnaryFunction<R(*)(P1), A1> {
typedef R result_type;
};

(***)


>> Similarly, std::stack<> and other container adaptors do not require
me
>> explicitly telling them that my underlying container has back(),
>> push_back() and pop_back() before use.


That would be handled automatically for the standard containers, and
the implementors of new containers would just declare that their
containers model the standard Back_extensible concept.


>> However, I would like to see a mechanism to tell std::stack to do
>> some sanity checks, without requiring excessive verbiage from user.


Maybe static asserts are for you?


>> I guess, I could go through the STL components and exhibit one by
>> one examples that defeat your claim.


My claim is that the STL relies on explicit conformance declarations
in the form of iterator_category. I don't see how anything you've
said defeats that.


>> Concepts for C++ ought to go beyond the iterator theory. It ought
to
>> support other styles and manifestly sound techniques.


Absolutely. Systems that require explicit conformance declarations
have a long history of success.


>> From my perspective, the real point of the iterator theory is not
>> that we can do *, ++, -- or iterator<T>::iterator_category. The
real
>> point is that of classification of algorithms based on "minimal
>> requirements", that turns classical problem of
>>
>> number of data structures =D7 number of algorithms
>>
>> into a (quasi-)linear problem.


Also the ability to refine those algorithms to run more efficiently
when information is available that allows us to do so. That's the key
feature provided by iterator_category.


>> Before closing this unusually lengthy message, let me give an
example of
>> a technique that a compulsory explicit conformance makes exceedingly
>> complicated or downright outlawed: effective overloading based on
>> concepts.


I'm sorry, I think you must really misunderstand the proposal.
Concept-based overloading (even of the kind you describe) is an
essential part of what it allows.

>> Overloading usually assumes a set of candidates, out which only one
>> will be selected. In the compulsory explicit conformance scenario,
as a
>> template library author I cannot effectively use helper functions
(in
>> form of overloads) in my implementations. The only way I could
>> possibly get away will be to require users to make explicit
>> conformance statements to my own implementation detail concepts used
>> in my implementation internal functions. And, doing so require me
>> exposing the implementation details in the documentation.


No, you just need to make sure the requirements ("where" clauses) of
your helper functions are a subset of the requirements of the function
template that calls them. It's pretty simple actually.

====

(**) For the record, I happen to love the Python language.

====

(***) A declaration of std::transform might look like:

template <typeid In, typeid Out, typeid F>
where {
InputIterator<In>,
OutputIterator<
Out,
Unaryfunction<F, InputIterator<In>::value_type>::result_type
>,
}
Out transform(In i1, In i2, Out i3, F f);

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

Thomas Witt

unread,
Apr 13, 2005, 4:17:58 PM4/13/05
to
Gabriel Dos Reis wrote:
>
> Overloading usually assumes a set of candidates, out which only one
> will be selected. In the compulsory explicit conformance scenario,
as a
> template library author I cannot effectively use helper functions (in
> form of overloads) in my implementations. The only way I could
> possibly get away will be to require users to make explicit
> conformance statements to my own implementation detail concepts used
> in my implementation internal functions. And, doing so require me
> exposing the implementation details in the documentation.
> I do not call that abstraction, nor modularity.
> Furthermore, since overloads include candidates that will fail,
> neither me, nor the user has any clue about what concepts the
explicit
> conformance statement should mention.

I can see this being an argument for being able to only require
structural
conformance in some cases, because structural conformance is the only
thing that the library author can rely on internally. As soon as the
internal overloading involves semantic properties of the type the
library author must ask the library user for information. In other word
in this case it is impossible to hide this implementation detail from
the user.

The question is whether named conformance should be required in cases
where semantics are involved. I think so.

Thomas

Alberto Barbati

unread,
Apr 14, 2005, 3:06:38 AM4/14/05
to
wka...@yahoo.com wrote:
>
> A possible split-the-difference compromise here is some kind of
> "meta-try" construct. On possible syntax:
>
> <? [token-sequence1] ?- [token-sequence2]
> ?- [token-sequence3] ... ?>
>
> The first token sequence that does not cause a compilation error
> (within the class or function) is the sequence used.
>
> Using this construct, your example becomes:
>
> template<typename T>
> class X : public T {
> public:
> void Dump() {
> std::cout << "In X<T>::Dump()" << std::endl;
>
> <? T::Dump();
> ?- std::cout << "T::Dump() not valid" << std::endl;
> ?>
> }
> };

Looks interesting to me. About the syntax, I would prefer to have braces
somewhere, in order to make scope rules clearer. In order to avoid
introducing new keywords we could "overload" existing ones instead of
using tokens made of punctuation characters. For example, I find this
syntax quite intriguing:

template<typename T>
class X : public T
{
public:
void Dump()
{
std::cout << "In X<T>::Dump()" << std::endl;

static try
{
T::Dump();
}
else try
{


std::cout << "T::Dump() not valid" << std::endl;
}
}

};

Notice that the keyword "try" must be followed by a braced-enclosed
statement (as in a regular try-block).

What happens when all proposed blocks won't compile? We have two
reasonable choices: either do nothing or give an error. We could extend
the syntax to provide an "else" block that won't be "tried", but must be
valid if it's reached:

void Dump()
{
static try
{
T::Dump();
}
else try
{
std::cout << *this << std::endl;
}
else
{
// if we reach this block, it must be valid
std::cout << "don't know how to dump" << std::endl;
}
}

There can be any number of "else try" blocks but only one "else" block
and the "else" block must be the last one. If the "else" block is
missing, it's the same as if there is a "else {}".

A different approach could be to require that at least one block is
good, thus forcing the programmer to put an "else try {}" at the end if
such is the intended behaviour.

> The big issue for me with the VC++ approach is that it is just
> the beginning of a big ugly list of "meta-predicates" corresponding
> to the possible usages of scoped identifiers. SFINAE and
> this meta-try approach avoid this, because the desired usage of
> identifier(s) are given simply by using them.

Agreed.

> This sort of meta-try makes it more explicit than SFINAE what is
> optional and what isn't. The meta-try is also more granular. I
> would guess that the meta-try is easier to implement than SFINAE,
> but I don't know enough about compiler implementation for my
> opinion to be worth much.

I don't know enough about compiler internals either...

> It may be possible allow the usage of the meta-try outside of
> template defintions, so it could be used for preprocessor
> metaprogramming as well.

I'm not sure it would be a good idea. I feel that a "static try"
statement should be confined inside a template-controlled block, but it
just a personal feeling. BTW, the syntax I proposed is only suitable at
function scope; wheter this is good or bad, I don't know.

Alberto

Gabriel Dos Reis

unread,
Apr 14, 2005, 9:03:47 AM4/14/05
to
"Thomas Witt" <thoma...@gmail.com> writes:

| Gabriel Dos Reis wrote:
| >
| > Overloading usually assumes a set of candidates, out which only one
| > will be selected. In the compulsory explicit conformance scenario,
| as a
| > template library author I cannot effectively use helper functions (in
| > form of overloads) in my implementations. The only way I could
| > possibly get away will be to require users to make explicit
| > conformance statements to my own implementation detail concepts used
| > in my implementation internal functions. And, doing so require me
| > exposing the implementation details in the documentation.
| > I do not call that abstraction, nor modularity.
| > Furthermore, since overloads include candidates that will fail,
| > neither me, nor the user has any clue about what concepts the
| explicit
| > conformance statement should mention.
|
| I can see this being an argument for being able to only require
| structural
| conformance in some cases, because structural conformance is the only
| thing that the library author can rely on internally.

yes.

| As soon as the
| internal overloading involves semantic properties of the type the
| library author must ask the library user for information. In other word
| in this case it is impossible to hide this implementation detail from
| the user.

And the issue is whether we should take that as the rule and require
that every use of concepts shall be preceeded by a conformance
statement.

It is tempting to think that just because some cases need explicit
conformance statement, it follows that all uses shall fall in that
category.

| The question is whether named conformance should be required in cases
| where semantics are involved. I think so.

The distinction some people seem to overlook or miss is:

(1) compulsory conformance statement

versus

(2) optional conformance statement


The main point of (2) is that there are constructs where conformance
statement will be required, and there are other cases where it should
not be required. In other words, it should be optional not compulsory.


As a matter of fact, after further discussions between the various
proponents, it appears that the compulsory conformance camp recognizes
that if you walk your way through some of the examples presented and
try to express them in the other design, you more or less end up with
what the optional conformance camp is suggesting, except that you
would have added more exponential complecity in the process.

--
Gabriel Dos Reis
g...@integrable-solutions.net

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

Gabriel Dos Reis

unread,
Apr 14, 2005, 9:05:28 AM4/14/05
to
"dave_abrahams" <da...@boost-consulting.com> writes:

| Gabriel Dos Reis wrote:
|
|
| >> STL is a successful example of generic programming, as implemented
| >> by C++, but I disagree with your assertion that it relies on
| >> explicit conformance to a concept.
|
|
| It seems to me that if you accept these two points:
|
| 1. STL relies on iterator_traits<T>::iterator_category
|
| 2. providing iterator_traits<T>::iterator_category amounts to an
| explicit declaration of conformance to a concept.
|
| then you must accept that STL "relies on explicit conformance to a
| concept." So I presume you disagree with either 1 or 2 (or both).
| Which is it, please?

Or that you get your chain of inference wrong?

I do not dispute this:
1. some components of STL relies on iterator_traits<T>::iterator_category.
2. iterator_traits<T>::iterator_category is indeed morally explicit
conformance delcaration.

therefore its follows that *some* components of STL uses explicit
conformance.

Furthermore, I claim that concepts shall not be blindsighted by the
iterator theory.

--
Gabriel Dos Reis
g...@integrable-solutions.net

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

Allan W

unread,
Apr 15, 2005, 3:11:45 AM4/15/05
to
> wka...@yahoo.com wrote:
> >
> > A possible split-the-difference compromise here is some kind of
> > "meta-try" construct. On possible syntax:
> >
> > <? [token-sequence1] ?- [token-sequence2]
> > ?- [token-sequence3] ... ?>
> >
> > The first token sequence that does not cause a compilation error
> > (within the class or function) is the sequence used.

Alberto Barbati wrote:
> Looks interesting to me. About the syntax, I would prefer to have
braces
> somewhere, in order to make scope rules clearer. In order to avoid
> introducing new keywords we could "overload" existing ones instead of
> using tokens made of punctuation characters. For example, I find this
> syntax quite intriguing:
>
> template<typename T>
> class X : public T
> {
> public:
> void Dump()
> {
> std::cout << "In X<T>::Dump()" << std::endl;
>
> static try
> {
> T::Dump();
> }
> else try
> {
> std::cout << "T::Dump() not valid" << std::endl;
> }
> }
> };
>
> Notice that the keyword "try" must be followed by a braced-enclosed
> statement (as in a regular try-block).

Hmm, would you be able to use this to create a member function only
if the base class does not have it?

class Der : public Base {
/* ... */
static try {
using Base::Dump();
} else try {
void Dump() { /* ... */ }
}
};

Aren't we worried about paradoxes? Is it possible for two different
groups of static try() blocks each to depend on how the other one is
resolved? I wasn't able to construct an example, but that doesn't
mean there isn't one.

Loïc Joly

unread,
Apr 15, 2005, 3:19:29 AM4/15/05
to
dave_abrahams a écrit :

> For example, a type
> that is EqualityComparable doesn't just have an operator== and
> operator!= (structural properties). There is a special semantic
> relationship between these operators: they must be inverses of one
> another.

And it is still far from enough. Operator == must be reflective,
symetric and positive to be of any use. I have not seen any concept
proposal or anything else in C++ that would match this kind of
requirement. I guess we have no choices but trust the other developpers...

--
Loïc

Alberto Barbati

unread,
Apr 15, 2005, 5:28:35 AM4/15/05
to
Allan W wrote:
>
> Hmm, would you be able to use this to create a member function only
> if the base class does not have it?
>
> class Der : public Base {
> /* ... */
> static try {
> using Base::Dump();
> } else try {
> void Dump() { /* ... */ }
> }
> };
>
> Aren't we worried about paradoxes? Is it possible for two different
> groups of static try() blocks each to depend on how the other one is
> resolved? I wasn't able to construct an example, but that doesn't
> mean there isn't one.
>

As I said at the end of my post, that syntax is suitable only inside
function-blocks. Yours is a fine example of the dangers in allowing a
greater flexibility.

Alberto

wka...@yahoo.com

unread,
Apr 17, 2005, 2:00:17 PM4/17/05
to

Good point. Probably the only way to handle this issue is to
say that only compile errors detectable based on the portion of
the compilation unit prior to the meta-try will be used to select
which token sequence is chosen.

dave_abrahams

unread,
Apr 18, 2005, 5:02:44 AM4/18/05
to
We have begun to work together on some of these problems, so I'm not
going to continue this thread here. There is one inaccuracy that I
feel I would be remiss not to correct, though.

Gabriel Dos Reis wrote:

> As a matter of fact, after further discussions between the various
> proponents, it appears that the compulsory conformance camp
recognizes
> that if you walk your way through some of the examples presented and
> try to express them in the other design, you more or less end up with
> what the optional conformance camp is suggesting, except that you
> would have added more exponential complecity in the process.

I'm afraid you have misunderstood what the explicit conformance camp
recognizes. The only exponential complexity we have identified was
mathematically proven by Jaakko Jarvi to occur for certain kinds of
conformance assertions, and it applies to any design you choose.

--
Dave Abrahams
Boost Consulting

http://www.boost-consulting.com

Gabriel Dos Reis

unread,
Apr 18, 2005, 11:12:07 AM4/18/05
to
"dave_abrahams" <da...@boost-consulting.com> writes:

| We have begun to work together on some of these problems, so I'm not
| going to continue this thread here. There is one inaccuracy that I
| feel I would be remiss not to correct, though.

I'm sending this to clarify that the "perceived inaccurary" is just
perceived, not real. And this is going to be my last message on this
topic here as well.

| Gabriel Dos Reis wrote:
|
| > As a matter of fact, after further discussions between the various
| > proponents, it appears that the compulsory conformance camp
| recognizes
| > that if you walk your way through some of the examples presented and
| > try to express them in the other design, you more or less end up with
| > what the optional conformance camp is suggesting, except that you
| > would have added more exponential complecity in the process.
|
| I'm afraid you have misunderstood what the explicit conformance camp
| recognizes.

I don't think so, as I wrote to Jaakko when he asked.
(The matter is even more confused as the compulsory conformance camp
had so many different people to talk to at the same same, so what one
representative heard had to be repeated to another one).

There were two sorts of complexities:
(1) what user has to write;
(2) what compiler has to do.

Jaakko was dealing with (2).
(1) was discussed while Jaakko was working on (2). Ask the
representatives of compulsory conformance statement camp who were
discussing issues with us while Jaakko was working on (2). When the
complexity of (1) was discussed, it was colloquially referred to as
"to translate the concept Small in the other design, we need a concept
Smaller, and Even_smaller, and ...".


We believe (1) is a very fundamental issue that should not be
underestimated; it was and is at the core of the paper we presented.
That is clearly recognized and emphasized in the two sub-sections
"Programming problems" and "Implementation problems".
I think, those still remain the key questions to be answered or
approximated adequately.

--
Gabriel Dos Reis
g...@cs.tamu.edu
Texas A&M University -- Department of Computer Science
301, Bright Building -- College Station, TX 77843-3112

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

doug....@gmail.com

unread,
Apr 19, 2005, 6:22:22 PM4/19/05
to
Gabriel Dos Reis wrote:
> (The matter is even more confused as the compulsory conformance camp
> had so many different people to talk to at the same same, so what one
> representative heard had to be repeated to another one).

This happens even with two authors :)

> There were two sorts of complexities:
> (1) what user has to write;
> (2) what compiler has to do.
>
> Jaakko was dealing with (2).
> (1) was discussed while Jaakko was working on (2). Ask the
> representatives of compulsory conformance statement camp who were
> discussing issues with us while Jaakko was working on (2).

I think that would include me, so...

> When the
> complexity of (1) was discussed, it was colloquially referred to as
> "to translate the concept Small in the other design, we need a
concept
> Smaller, and Even_smaller, and ...".

Something got lost in the colloquiallism here and caused this
misunderstanding. In essence, "one could do this" got translated into
"one must do this". The "Small" example referred to looks like this:

template<typeid T, int N>
concept Small
{
require sizeof(T) <= N;
};

template<typeid T> void f(const T&);
template<typeid T> where { Small<T, 200> } void f(const T&);

template<typeid T>
void foo(const T& t)
{
f(t);
}

The question asked was: Suppose that the user does not--and should
not--know about the Small concept, because "f" is buried in the
library. How does the Indiana proposal (i.e., the compulsory
conformance statement camp, although we use the term "explicit model
declaration") address this? The answer is to write a model template
like this:

template<typename T, int N>
where { sizeof(T) <= N }
model Small { };

Now, when the compiler needs to find a model for Small<T, N>, it will
see this template, check the where clause, and if it matches the model
will be instantiated, so we have the model declaration/conformance
statement for the compiler. The important takeaways are:

1) We did an "approximate" structural check before instantiating this
model template. Had it failed, the type & int value would not model the
concept.
2) The user never needs to know about Small, because the library
writer wrote the Small concept and the model template.

I think we were all in agreement up to there (but correct me if I'm
wrong!). However, we diverged when we started talking about the effects
with more complicated concepts, probably because we were talking
abstractly instead of writing concrete code to discuss. So...

Suppose Small is no longer a trivial concept, but something with a
bunch of requirements. How do we attack this problem? Well, we can
write a model template that checks lots of these structural
characteristics if we want. Or, we can pick one property that is
representative (e.g., iterator_category, result_type, iterator) to
check. Here's an example from the Indiana proposal, N1758, which adapts
existing mutable forward iterators to the new MutableForwardIterator
concept:

template<typename OldIter>
where { Convertible<typename
std::iterator_traits<OldIter>::category*,
std::forward_iterator_tag*>,
is_same<typename
std::iterator_traits<OldIter>::reference,
typename
std::iterator_traits<OldIter>::value_type&>::value }
model MutableForwardIterator<OldIter> {};

If I remember correctly, another question about explicit model
declarations popped up, regarding what happens if I have an Addable<T>
concept and you have an MyAddable<T> concept. Say the user has already
written a model declaration stating that type X models MyAddable:

model MyAddable<X> { };

Now that user tries to use my library, which requires Addable<X>. Does
the user have to write models for Addable for every single type she has
written, even though she already went through to add MyAddable model
declarations? Well, there's a better way: write a model template that
adapts MyAddables to Addables:

template<typeid T>
where { MyAddable<T> }
model Addable<T> { };

If you want to go the other way, do this:

template<typeid T>
where { Addable<T> }
model MyAddable<T> { };

So now someone writes the SuperAddable concept, which is the same as
MyAddable and Addable but with a different name! How many more model
definitions do we need? Just two: one to map from SuperAddable to
either Addable or MyAddable, and one to map back.

I think this is where the misunderstanding crept in... it's easy to
look at the above example and conclude that we need 4 model
declarations, then when someone "invents" UeberAddable we need another
6, etc, etc, on to exponentiality. This is not the case.

> We believe (1) is a very fundamental issue that should not be
> underestimated; it was and is at the core of the paper we presented.

Nor should it be overestimated.

We're dealing with an unknown here. How many model
declarations/conformance statements will the user need to write? Nobody
will know until we try it. We've been actively working to find ways to
reduce the number of model declarations that the user would have to
write. I've shown two of them here:

1) Use some structure to infer model declarations (e.g., the model
templates for Small and MutableForwardIterator)
2) Use existing concepts to infer model declarations (e.g., the model
templates for Addable and MyAddable).

There are other options as well that have come up in various
discussions that were omitted from our proposal [*], including:
3) Simplifying model declaration syntax when one is writing a new
class, e.g., allowing one write an abbreviated model declaration within
the scope of a class body. This could be syntactically similar to the
Texas proposal's "Encapsulated asserts", although the semantics I have
in mind do not match what is described in N1782.
4) Providing more concepts that aggregate common concepts together.
For instance, the Arithmetic<T> concept could cover
EqualityComparable<T>, LessThanComparable<T>, Assignable<T>,
CopyConstructible<T>, DefaultConstructible<T>, Addable<T>,
Subtractable<T>, etc, etc. These might more naturally use their
algebraic names, e.g., Ring<T> or AbelianGroup<T>, which imply more
semantics.
5) Compiler-defined models for purely structural things, e.g.,
DefaultConstructible or Callable.
6) Purely structural concepts, e.g., one could write:

template<typename T>
struct concept EqualityComparable {
bool operator==(const T&, const T&);
bool operator!=(const T&, const T&);
};

For this concept, model declarations would not be compulsory.
They could, of course, be provided to check conformance or to adapt the
syntax, as in the Texas proposal.
7) Including a primitive for structural checks in where clauses,
e.g.,

template<typename T>
where { struct EqualityComparable<T> }
model EqualityComparable<T> { };

This formulation is similar to the Small model template, but we
would essentially try to check all of the structure of
EqualityComparable before instantiating the model. This, like (6),
would require implementors to be able to do arbitrary instantiations
and checks then be able to silently "back out" if those checks fail.
The Indiana proposal intentionally avoids requiring this behavior: if
one writes things that are not SFINAE-safe in a model template's where
clause, and that model template gets instantiated for a type that
doesn't really work, the compiler prints a diagnostic and aborts. No
back-tracking required. Adding this feature would require the same
back-tracking required by the Texas proposal (although it would not be
the primary mechanism: named/nominal/explicit conformance is still the
common methodology).

We care very deeply about the user experience, of course, because we
will be the users of whatever concept system makes it into C++.
However, over the years we have realized that explicit model
declarations/compulsory conformance statements are absolutely necessary
for many interesting concepts. We've seen them needed for iterators,
graphs, abstract algebra, linear algebra, parallel programming, etc.,
and they are emulated with category tags (like iterator_category in the
C++ Standard Library). For more information, refer to my recent post
"Expicit model definitions are necessary" on the -ext reflector, which
will also be a part of the post-meeting mailing.

One thing that would greatly help the dialog on this major issue is to
have a paper that rebuts the arguments in that paper if someone
believes that the arguments are not sound. Additionally, it would be
helpful to have a paper that argues why model declarations/conformance
statements cannot be required, e.g., because they can't be written for
certain things or because there are examples where there truly is a
huge number of model declarations to write.

> That is clearly recognized and emphasized in the two sub-sections
> "Programming problems" and "Implementation problems".

We've already hit on many of the comments in the "Programming problems"
section, but there are clearly more that we could address. Let's do
that offline and, if the results are interesting enough, put the
results into one of the mailings.

> I think, those still remain the key questions to be answered or
> approximated adequately.

Sure, but we have to be careful. We had a discussion and then took away
completely different impressions of what the discussion meant. Nobody
that is an author of either proposal can be unbiased, but we (and I
meant *everyone*, myself included of course) need to look through the
bias to approximate the true "cost" of going in either direction.

This is my last message on the subject in this forum. The details
surrounding concepts extensions in C++ are numerous and the arguments
often subtle, requiring a deep understanding of both concepts proposals
and a extensive experience with generic programming itself.

Best regards,
Doug Gregor
dgr...@cs.indiana.edu

[*] Note that I am in some cases inventing syntax in this list of
ideas, so not every feature has received the careful consideration of
having been explained to others and written down in a proposal.
Consider this a brainstorming list, not a list of intended changes or
fixes to the Indiana proposal.

0 new messages