Topic: overriding method with derived class return type
Author: "Bradd W. Szonye" <bradds@concentric.net>
Date: 1997/07/09 Raw View
Eric Tse <etse@scdt.intel.com> wrote in article
<01bc8bbf$d3bd1550$8376b78f@sccia135>...
> Hi everyone,
>
> Thanks for the response. But how would one use this feature? I have
> included a sample code which I could not tried (I don't have a compiler
> which support covariance), and wonder if it is one of the usage example.
> This example shows the ability to get to a derived class without explicit
> dynamic casting. Is there other usages? Thanks,
Keep in mind that a covariant return type isn't necessarily Base/Derived.
It can also be a related inheritance lattice. This is the kind of feature
that I haven't wanted very often, but wanted it badly when I did. Consider
this excerpt from a class lattice:
class Automobile
{
virtual SparkPlug & spark_plugs() = 0;
virtual ~Automobile();
};
class ChevroletAutomobile : public Automobile
{
ACDelcoSparkPlug & spark_plugs() = 0;
};
class Corvette : public ChevroletAutomobile
{
ACDelcoSparkPlug & spark_plugs();
}
class ToonCar : public Automobile
{
AcmeSparkPlug & spark_plugs();
}
(Assume that AC Delco & Acme spark plugs are subclasses of SparkPlug, and
apologies to folks who know more about Chevrolets than I do.) When you know
you're working with a particular subclass--for example, ToonCars--it's
easier, safer, and possibly faster to get the covariant return type
directly rather than sprinkling your code with static_casts and
dynamic_casts. After all, ToonCars always use spark plugs from the Acme
Corporation, so it's better to express this in the interface than to rely
on documentation. It prevents implementors, maintainers, and users from
making mistakes. Consider the alternative, where ToonCar::spark_plugs()
returns a "generic" SparkPlug and users always cast the result to
AcmeSparkPlug. Eventually, a maintainer or user will ignore the
documentation and try to create a ToonCar with ACDelcoSparkPlugs. The
compiler won't complain, but the ubiquitous (and correct) casts to
AcmeSparkPlug will kill the program, possibly without a clue as to why.
--
Bradd W. Szonye
bradds@concentric.net
http://www.concentric.net/~Bradds
---
[ 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 ]
[ FAQ: http://reality.sgi.com/employees/austern_mti/std-c++/faq.html ]
[ Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu ]
Author: "Roger L. Cauvin" <rcauvin@homemail.com>
Date: 1997/07/09 Raw View
Pete Becker <petebecker@acm.org> wrote in article
<33C24834.1074806B@acm.org>...
> Roger L. Cauvin wrote:
[snip]
> > Yes, covariant return types are part of the committee draft standard.
> > Unfortunately, most compilers don't support them yet.
>
> Who, besides Microsoft, hasn't gotten around to implementing this
> yet? It's been in the language definition for a couple of years.
I spoke too soon. Microsoft Visual C++ and the HP compiler I use doesn't
support them at all, and the first Borland C++ 5.0 release had bugs in its
support of them, but I was only speculating about other compilers.
Does the g++ compiler support them? Symantec C++? Metrowerks Codewarrior
C++? I'd like to know.
---
Roger L. Cauvin
rcauvin@homemail.com
Technologist
ObjectSpace
---
[ 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 ]
[ FAQ: http://reality.sgi.com/employees/austern_mti/std-c++/faq.html ]
[ Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu ]
Author: Christian Millour <chris161@club-internet.fr>
Date: 1997/07/09 Raw View
Pete Becker wrote:
>
> Roger L. Cauvin wrote:
> >
> > Yes, covariant return types are part of the committee draft standard.
> > Unfortunately, most compilers don't support them yet.
>
> Who, besides Microsoft, hasn't gotten around to implementing this
> yet? It's been in the language definition for a couple of years.
pointer adjustments are still problematic, in non-trivial cases.
Uncommenting the definition for class D leads to the following
error message (g++ 2.7.2)
cova.cc:21: sorry, not implemented: adjusting pointers for covariant
returns
unfortunately, no warning is provided for class C (see ap2 below).
bash$ cat cova.cc
class A {
public:
virtual ~A() {}
virtual A* cov() { return this; }
int i;
};
class B {
public:
virtual ~B() {}
int j;
};
class C : public B, public A {
public:
virtual C* cov() { return this; }
int k;
};
#ifdef NOT_IMPLEMENTED
class D : public A {
virtual C* cov() { return 0; }
};
#endif
#include <iostream.h>
#define SHOW(form) cerr << #form << " " << form << endl
int main() {
C* cp = new C;
SHOW(cp);
A* ap1 = cp;
SHOW(ap1);
A* ap2 = ap1->cov();
SHOW(ap2);
A* ap3 = cp->cov();
SHOW(ap3);
}
bash$ g++ -Wall -pedantic cova.cc -o cova
bash$ cova
cp 0x804d620
ap1 0x804d628
ap2 0x804d620 // OUCH!
ap3 0x804d628
bash$
---
[ comp.std.c++ is moderated. To submit articles: Try just posting with your
newsreader. If that fails, use mailto:std-c++@ncar.ucar.edu
comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
Comments? mailto:std-c++-request@ncar.ucar.edu
]
Author: "Roger L. Cauvin" <rcauvin@homemail.com>
Date: 1997/07/09 Raw View
Pete Becker <petebecker@acm.org> wrote in article
<33C24834.1074806B@acm.org>...
> Roger L. Cauvin wrote:
[snip]
> > Yes, covariant return types are part of the committee draft standard.
> > Unfortunately, most compilers don't support them yet.
>
> Who, besides Microsoft, hasn't gotten around to implementing this
> yet? It's been in the language definition for a couple of years.
I spoke too soon. Microsoft Visual C++ and the HP compiler I use doesn't
support them at all, and the first Borland C++ 5.0 release had bugs in its
support of them, but I was only speculating about other compilers.
The Borland problems were actually rather obscure: within a diamond-shaped
hierarchy, with a virtual member function declared in the base class and
overridden with covariant return types in all of the derived classes. The
compiler choked when trying to compile the class definition of the
most-derived class.
Does the g++ compiler support covariant return types? Symantec C++?
Metrowerks Codewarrior C++? I'd like to know.
---
Roger L. Cauvin
rcauvin@homemail.com
Technologist
ObjectSpace
---
[ comp.std.c++ is moderated. To submit articles: Try just posting with your
newsreader. If that fails, use mailto:std-c++@ncar.ucar.edu
comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
Comments? mailto:std-c++-request@ncar.ucar.edu
]
Author: Christian Millour <chris161@club-internet.fr>
Date: 1997/07/10 Raw View
Bradd W. Szonye wrote:
When you know
> you're working with a particular subclass--for example, ToonCars--it's
> easier, safer, and possibly faster to get the covariant return type
> directly rather than sprinkling your code with static_casts and
> dynamic_casts. After all, ToonCars always use spark plugs from the Acme
> Corporation, so it's better to express this in the interface than to rely
> on documentation. It prevents implementors, maintainers, and users from
> making mistakes. Consider the alternative, where ToonCar::spark_plugs()
> returns a "generic" SparkPlug and users always cast the result to
> AcmeSparkPlug. Eventually, a maintainer or user will ignore the
> documentation and try to create a ToonCar with ACDelcoSparkPlugs. The
> compiler won't complain, but the ubiquitous (and correct) casts to
> AcmeSparkPlug will kill the program, possibly without a clue as to why.
hmmm. I used to be a fan of covariant return. I desisted when my
favourite compiler shown trouble converting to any but the leftmost
ancestor (see my other post in this thread). I now mostly use the
following approach, maybe a bit verbose, but which should
work with every compiler even for complex inheritance lattices :
class Automobile
{
virtual SparkPlug & spark_plugs() = 0;
virtual ~Automobile();
};
class ToonCar : public Automobile
{
AcmeSparkPlug & acme_spark_plugs();
SparkPlug & spark_plugs() { return acme_spark_plugs(); }
};
Clients of ToonCar that need to get the covariant type directly
can use acme_spark_plug(). As a bonus, they save the cost of virtual
dispatch.
---
[ 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 ]
[ FAQ: http://reality.sgi.com/employees/austern_mti/std-c++/faq.html ]
[ Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu ]
Author: heh@beamtech.ANTISPAM.com (Howard E. Hinnant)
Date: 1997/07/10 Raw View
In article <01bc8c82$96cebea0$4d2e5380@rcauvin.objectspace.com>, "Roger L.
Cauvin" <rcauvin@homemail.com> wrote:
>
> Does the g++ compiler support covariant return types?
Yes, but unsure about complex situations you mentioned with Borland.
> Symantec C++?
Don't know
> Metrowerks Codewarrior C++? I'd like to know.
No.
-Howard
---
[ comp.std.c++ is moderated. To submit articles: Try just posting with your
newsreader. If that fails, use mailto:std-c++@ncar.ucar.edu
comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
Comments? mailto:std-c++-request@ncar.ucar.edu
]
Author: James Kanze <james-albert.kanze@vx.cit.alcatel.fr>
Date: 1997/07/10 Raw View
"Roger L. Cauvin" <rcauvin@homemail.com> writes:
|> Pete Becker <petebecker@acm.org> wrote in article
|> <33C24834.1074806B@acm.org>...
|> > Roger L. Cauvin wrote:
|>
|> [snip]
|>
|> > > Yes, covariant return types are part of the committee draft standard.
|> > > Unfortunately, most compilers don't support them yet.
|> >
|> > Who, besides Microsoft, hasn't gotten around to implementing this
|> > yet? It's been in the language definition for a couple of years.
|>
|> I spoke too soon. Microsoft Visual C++ and the HP compiler I use doesn't
|> support them at all, and the first Borland C++ 5.0 release had bugs in its
|> support of them, but I was only speculating about other compilers.
|>
|> Does the g++ compiler support them? Symantec C++? Metrowerks Codewarrior
|> C++? I'd like to know.
G++ (2.7.2) doesn't. Sun CC (4.1) doesn't either. In practice, both of
these compilers are fairly stable, and were released some time ago, when
one couldn't have expected it.
I don't know about the new HP compiler. Since it is not object
compatible with the old one, we can't upgrade until all our third party
library suppliers have. Even then... The new compiler implements a
different language than the old one. We don't know if our code will
even compile with it, much less work. So introduction will be very
gradual.
--
James Kanze home: kanze@gabi-soft.fr +33 (0)1 39 55 85 62
office: kanze@vx.cit.alcatel.fr +33 (0)1 69 63 14 54
GABI Software, Sarl., 22 rue Jacques-Lemercier, F-78000 Versailles France
-- Conseils en informatique industrielle --
---
[ comp.std.c++ is moderated. To submit articles: Try just posting with your
newsreader. If that fails, use mailto:std-c++@ncar.ucar.edu
comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
Comments? mailto:std-c++-request@ncar.ucar.edu
]
Author: "Bradd W. Szonye" <bradds@concentric.net>
Date: 1997/07/12 Raw View
Christian Millour <chris161@club-internet.fr> wrote in article
<33C3D91E.223DA024@club-internet.fr>...
>
> class Automobile
> {
> virtual SparkPlug & spark_plugs() = 0;
> virtual ~Automobile();
> };
>
> class ToonCar : public Automobile
> {
> AcmeSparkPlug & acme_spark_plugs();
> SparkPlug & spark_plugs() { return acme_spark_plugs(); }
> };
>
> Clients of ToonCar that need to get the covariant type directly
> can use acme_spark_plug(). As a bonus, they save the cost of virtual
> dispatch.
I like this solution. It fits in with my philosophy of "overload names only
when absolutely necessary." However, I wouldn't strip the 'virtual' from
the new method:
class AcmeSuperSparkPlug: public AcmeSparkPlug { /* ... */ };
class WogerWabbitToonCar: public ToonCar
{
virtual AcmeSuperSparkPlug & super_spark_plugs();
AcmeSparkPlug & acme_spark_plugs() { return super_spark_plugs(); }
SparkPlug & spark_plugs() { return super_spark_plugs(); }
};
It also concerns me a bit that this technique can lead to massive interface
bloat in all but the simplest covariant lattices. Covariant return types
(when they work properly) I think have a nice balance between the
advantages and perils of syntactic sugar.
--
Bradd W. Szonye
bradds@concentric.net
http://www.concentric.net/~Bradds
---
[ 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 ]
[ FAQ: http://reality.sgi.com/employees/austern_mti/std-c++/faq.html ]
[ Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu ]
Author: "Eric Tse" <etse@scdt.intel.com>
Date: 1997/07/07 Raw View
Hi everyone,
I read news awhile back about the following feature going to be in the
standard. Is it true?
The feature is that a derived class could override a base class method with
the same signature but different return type, the the return type is one of
the derived class of the original method's return type. For example:
class A {
virtual A* get();
};
class B : public A {
virtual B* get();
};
Thanks,
Eric
--
--------------------------------------------------------------------
Eric Tse Design Technology, Microprocessor Products Group
Intel Corporation email: etse@scdt.intel.com
M/S RN4-38, 2200 Mission College Blvd. phone: (408)765-8453
Santa Clara, CA 95052 fax: (408)765-5278
--------------------------------------------------------------------
---
[ comp.std.c++ is moderated. To submit articles: Try just posting with your
newsreader. If that fails, use mailto:std-c++@ncar.ucar.edu
comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
Comments? mailto:std-c++-request@ncar.ucar.edu
]
Author: kuehl@horn.informatik.uni-konstanz.de (Dietmar Kuehl)
Date: 1997/07/07 Raw View
Hi,
Eric Tse (etse@scdt.intel.com) wrote:
: I read news awhile back about the following feature going to be in the
: standard. Is it true?
: The feature is that a derived class could override a base class method with
: the same signature but different return type, the the return type is one of
: the derived class of the original method's return type. For example:
: class A {
: virtual A* get();
: };
: class B : public A {
: virtual B* get();
: };
Yes, this is in the standard (but I don't have a reference to the
standard handy). This feature is called "covariance". However, only
few compilers currently support it (eg. gcc does).
--
<mailto:dietmar.kuehl@uni-konstanz.de>
<http://www.informatik.uni-konstanz.de/~kuehl/>
I'm seeking a new employment - See my homepage for details
---
[ comp.std.c++ is moderated. To submit articles: Try just posting with your
newsreader. If that fails, use mailto:std-c++@ncar.ucar.edu
comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
Comments? mailto:std-c++-request@ncar.ucar.edu
]
Author: jim.hyslop@leitch.com (Jim Hyslop)
Date: 1997/07/08 Raw View
In article <01bc8afa$14c98720$8376b78f@sccia135> on 07 Jul 1997
10:45:00 PDT, etse@scdt.intel.com says...
> Hi everyone,
>
> I read news awhile back about the following feature going to be in the
> standard. Is it true?
>
> The feature is that a derived class could override a base class method with
> the same signature but different return type, the the return type is one of
> the derived class of the original method's return type.
Yep, it's true! Specifically, a function can be overloaded if the
return types are "covariant", which is defined in section 10.3:
-- begin quote from CD2 --
5 ... If a function D::f overrides a function B::f, the return types
of the functions are covariant if they satisfy the following criteria:
both are pointers to classes or references to classes
the class in the return type of B::f is the same class as the class
in the return type of D::f or, is an unambiguous direct or indirect
base class of the class in the return type of D::f and is accessible
in D
both pointers or references have the same cv-qualification and the
class type in the return type of D::f has the same cv-qualification
as or less cv-qualification than the class type in the return type
of B::f.
-- end quote from CD2 --
The example you posted,
> class A {
> virtual A* get();
> };
>
> class B : public A {
> virtual B* get();
> };
will work because it satisfies all three criteria.
--
Jim Hyslop O-
Shamelessly purloined from someone else's sig:
He who lives in glass houses should not invite he who is without sin.
Don't pass on that email about someone dying of cancer or a "new
super-powerful computer virus" until you check out the Internet Hoax
page http://ciac.llnl.gov/ciac/CIACHoaxes.html first!
---
[ 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 ]
[ FAQ: http://reality.sgi.com/employees/austern_mti/std-c++/faq.html ]
[ Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu ]
Author: "Roger L. Cauvin" <rcauvin@homemail.com>
Date: 1997/07/08 Raw View
Eric Tse <etse@scdt.intel.com> wrote in article
<01bc8afa$14c98720$8376b78f@sccia135>...
> Hi everyone,
>
> I read news awhile back about the following feature going to be in the
> standard. Is it true?
>
> The feature is that a derived class could override a base class method with
> the same signature but different return type, the the return type is one of
> the derived class of the original method's return type.
[example deleted]
Yes, covariant return types are part of the committee draft standard.
Unfortunately, most compilers don't support them yet.
---
Roger L. Cauvin
rcauvin@homemail.com
Technologist
ObjectSpace
---
[ 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 ]
[ FAQ: http://reality.sgi.com/employees/austern_mti/std-c++/faq.html ]
[ Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu ]
Author: "Eric Tse" <etse@scdt.intel.com>
Date: 1997/07/08 Raw View
Hi everyone,
Thanks for the response. But how would one use this feature? I have
included a sample code which I could not tried (I don't have a compiler
which support covariance), and wonder if it is one of the usage example.
This example shows the ability to get to a derived class without explicit
dynamic casting. Is there other usages? Thanks,
Eric
#include <iostream.h>
class A {
public:
virtual A* get()
{
return a_;
}
virtual int getA()
{
return 1;
}
static A* a_;
};
class B : public A {
public:
virtual B* get()
{
return b_;
}
virtual int getB()
{
return 2;
}
static B* b_;
};
A* A::a_ = new A();
B* B::b_ = new B();
int main()
{
A* a = new B();
B* b = a->get(); // ************************ //
cout << b->getB() << endl;
return 0;
}
---
[ 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 ]
[ FAQ: http://reality.sgi.com/employees/austern_mti/std-c++/faq.html ]
[ Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu ]
Author: Pete Becker <petebecker@acm.org>
Date: 1997/07/08 Raw View
Roger L. Cauvin wrote:
>
> Eric Tse <etse@scdt.intel.com> wrote in article
> <01bc8afa$14c98720$8376b78f@sccia135>...
> > Hi everyone,
> >
> > I read news awhile back about the following feature going to be in
> the
> > standard. Is it true?
> >
> > The feature is that a derived class could override a base class
> method with
> > the same signature but different return type, the the return type is
> one of
> > the derived class of the original method's return type.
>
> [example deleted]
>
> Yes, covariant return types are part of the committee draft standard.
> Unfortunately, most compilers don't support them yet.
Who, besides Microsoft, hasn't gotten around to implementing this
yet? It's been in the language definition for a couple of years.
-- Pete
---
[ comp.std.c++ is moderated. To submit articles: Try just posting with your
newsreader. If that fails, use mailto:std-c++@ncar.ucar.edu
comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
Comments? mailto:std-c++-request@ncar.ucar.edu
]
Author: kuehl@horn.informatik.uni-konstanz.de (Dietmar Kuehl)
Date: 1997/07/09 Raw View
Hi,
Eric Tse (etse@scdt.intel.com) wrote:
: This example shows the ability to get to a derived class without explicit
: dynamic casting. Is there other usages?
[example reduced to a minimum]
: class A {
: public:
: virtual A* get()
: {
: return a_;
: }
: };
: class B : public A {
: public:
: virtual B* get()
: {
: return b_;
: }
: };
: A* a = new B();
: B* b = a->get(); // ************************ //
Actually, your use is not a legal use. The lookup of the type is made
at compile time: You can get a 'B*' if you use 'get()' on a 'B' (or
derived) object. You cannot get it from an 'A' object, even if you know
that the 'A' object referenced is actually a 'B' object.
The main use is to avoid casts if you use virtual functions for derived
objects. For example:
struct A {
virtual A *clone() const { return new A(*this); }
};
struct B {
virtual B *clone() const { return new B(*this); }
B *foo() const { /* do something */; return this; }
};
B b;
B *bptr = b.clone()->foo();
In this case, we and the compiler both know that 'b.clone()' returns a
'B*'. Thus, there is no need to cast the result and 'B' member
functions can be directly used on the result. Likewise, it is possible
to initialize a 'B*' without a cast, thereby increasing type safty (it
is checked at compile time, not at run-time).
--
<mailto:dietmar.kuehl@uni-konstanz.de>
<http://www.informatik.uni-konstanz.de/~kuehl/>
I'm seeking a new employment - See my homepage for details
---
[ 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 ]
[ FAQ: http://reality.sgi.com/employees/austern_mti/std-c++/faq.html ]
[ Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu ]
Author: heh@beamtech.com (Howard E. Hinnant)
Date: 1997/07/09 Raw View
In article <01bc8bbf$d3bd1550$8376b78f@sccia135>, "Eric Tse"
<etse@scdt.intel.com> wrote:
> Thanks for the response. But how would one use this feature? I have
My classic use is the clone method (a virtual copy constructor).
class A {
public:
virtual A* clone() const {return new A(*this);}
virtual ~A() {}
};
class B : public A {
public:
virtual B* clone() const {return new B(*this);}
};
int main()
{
B b;
B* bp = b.clone();
}
Because my compiler does not yet support covariant returns, I have to code:
B* bp = (B*)b.clone();
-Howard
---
[ 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 ]
[ FAQ: http://reality.sgi.com/employees/austern_mti/std-c++/faq.html ]
[ Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu ]