Topic: Associated member functions


Author: Sean Hunt <rideau3@gmail.com>
Date: Thu, 13 Dec 2007 14:46:12 CST
Raw View
Before anyone asks, this was a problem with the moderation. My first
post never made it through, so I was told to repost, and now it seems
that the first post has gone through and landed.

Still, does anyone have any comments/rationales I haven't thought of?

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Douglas Gregor <doug.gregor@gmail.com>
Date: Thu, 13 Dec 2007 14:58:43 CST
Raw View
On Dec 10, 2:24 pm, Sean Hunt <ride...@gmail.com> wrote:
> Given my understanding of the description of n2421, there are only
> three good cases whereby you should actually use an associated member
> definition. In all other cases, there is a superior, more compatible
> alternative (I'll get to that in a moment). Each of these cases is
> when there is absolutely no alternative - constructors, destructors,
> and assignment operators. This is because a concept map is forbidden
> from defining or declaring an associated member function.

Right.

> Rather than declaring the following:
>
> concept C <typename T>
> {
>     void T::foo();
>
> }
>
> You should declare:
>
> concept C <typename T>
> {
>     void foo (T& t) { late_check { t.foo(); } }
>
> }
>
> (The use of the late_check is due to my understanding of
> [ concept.fct]
> - the default implementation is a constrained template and so must use
> late_check to avoid the use of the archetype T', which doesn't include
> a member function foo. If this is incorrect, I would appreciate being
> corrected.)
>
> The latter version is identical to the first from the user's
> perspective. He can map the concept (or not, if it's auto) the same.

Your understanding of late_check is almost perfect. The late_check is
needed to do what you want, because without it the default
implementation of C::foo would not type-check. In this case, the use
of late_check will delay that type-checking until the compiler tries
to instantiate the default implementation of C::foo (i.e., when the
concept map doesn't contain a matching "foo" and there is no matching
free function "foo"). But, you knew all this :)

The issue is with auto concepts. If one tries to fulfill the
requirement C<X>, there are several possible cases:

  1) There is a concept map C<X> already; use that
  2) There is a concept map template that can be used to instantiate
C<X>; use that
  3) There is no concept map; if C is auto, try to match the syntax:
    a) There is a free function foo(X&) that works: the requirement
C<X> is fulfilled
    b) There is a member function X::foo() that works: the default
implementation of C::foo works, and the requirement C<X> is fulfilled
    c) There is no member function X::foo(), or there is a member
function but the call to it is ill-formed: the compiler emits an
error, and the program is ill-formed

The issue here is 3(c): you might have expected that the requirement
C<X> wouldn't hold if there is no member function "foo", but that's
not exactly how auto concepts work. auto concepts do try to match the
syntax of the concept, and if that syntax doesn't work, then the
requirement check for C<X> fails but the compiler does not emit any
errors. However, the syntax-matching is restricted to the syntax
described in the concept: so with the concept C, it can look for a
free function "foo", but once it tries to instantiate the late_check
block within the default implementation of C::foo it's already
committed to that path and can't back out. So, you see an error at
that point.

There is a workaround, but it's a bit verbose:

concept C <typename T>
{
    void foo (T& t);
}

auto concept MemberC<typename T>
{
  void T::foo();
}

template<MemberC T>
concept_map C<T> {
  void foo(T& t) { t.foo(); }
}

> But if he needs to change the implementation - crucial if he doesn't
> have access to the class in question - he can do so by simply
> modifying the function definition in the concept map.

Right.

> The template
> implementer just needs to rearrange the syntax, which is the heart of
> my disagreement.
>
> See, the problem is that this syntax represents a major step backward.
> Now, rather than using the object-oriented "." operator, one has to
> use the old, C-style method of passing the object itself. (there are
> differences like the use of references, but that's not the important
> point).

So, the first place where we disagree here is that I (personally!)
don't find the object-oriented "." syntax to be an improvement over
functional syntax. It leads to some oddities like "a.equals(b)" where
"a == b" would be more natural.

However, more important than my own preference is that there's already
a preference for free functions over member functions in C++. They
have uniform conversion rules, are easier to decouple from the
definition of the classes themselves, and, by expressing generic
algorithms using free functions rather than member functions, one can
use those algorithms on built-in types and types whose class
definitions are already "closed" (and, therefore, cannot be modified).
Concepts are following after that tradition.

> Proper OO is one of the major attractions of C++ - it doesn't
> make sense to make that mutually exclusive with constrained
> templates.
> If you try to use the first of my coding examples, allowing you to
> have a constrained template that interfaces in an object-oriented
> manner, then you sacrifice a third of C++'s (and C's) major axioms
> (no
> pun intended) - the avoidance of naming requirements for users.

There will always be specific naming requirements in a concept... what
you call a "foo", I will call a "bar", and so on. Concept maps give
you a way to adapt names (and signatures) appropriately, but to do so
you need to use the free-function notation within the concepts (and
the generic algorithms that use those concepts).

> My personal favored solution would be to allow a concept map (or
> concept default implementation) to define a member function for the
> archetype for any member function other than an assignment operator,
> constructor, or destructor. Special rules for definition of other
> functions within maps that must otherwise be members exist, clearly to
> protect those three functions. Wouldn't it just be better to actually
> protect them from instantiation?

I'm not sure what you mean by "protect them from instantiation". Do
you mean having special rules regarding the matching of constructors,
copy assignment operators, etc.?

> The only other reason I can think of
> is to prevent giving a builtin type a member function, but since
> concepts already abstract that, I don't see how that could be a
> problem (barring stupid uses of late_check).

That is an issue, both syntactic and semantic. If we want to write a
concept map that maps the syntax of the 'C' with a member function
'foo' for an int*, how do we do it?

  concept_map C<int*> {
    void int*::foo() { /* can that syntax ever work? */ }
  }

There are other issues, where different concept maps could provide
different definitions for the same member function in the same type
(as viewed through different concepts).

> Alternatively, it could be changed so that in the archetype T' of T,
> for each associated non-member function whose first parameter is a
> reference to T, T' has a member function of the same name, with the
> same reference qualifier as the associated function's first parameter,
> and with parameters of the same types as the other parameters, which
> just forwards the call.

To me, this feels a bit like a hack.

> Personally, I'd prefer the former, as it gives member function
> requirements a purpose - the latter is more of a way around the
> problem.

The former is a very good idea, but it runs into some implementation
problems (such as the parsing issue) and some conceptual problems
(X::foo could mean many different things through many different
concepts). Those problems may be solvable; I don't really know.

  - Doug

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]