Topic: C++0x, SFINAE, decltype: ill-formed or just a deduction failure?


Author: SG <s.gesemann@gmail.com>
Date: Fri, 16 Oct 2009 09:34:58 CST
Raw View
On 8 Sep., 20:46, Doug Gregor <doug.gre...@gmail.com> wrote:
> On Sep 7, 1:21 am, SG <s.gesem...@gmail.com> wrote:
> > My goal here is to see whether it's possible to test whether an
> > arbitrary expression that successfully parses is actually valid or not
> > via SFINAE+decltype.
>
> You can test arbitrary expressions, but there are two major
> limitations to (extended)SFINAE:
>
>  1) Access control still happens after template argument deduction,
> so you will get a hard error if your expression makes use of an
> inaccessible member.
>
>  2) If type-checking the expression causes the instantiation of any
> class templates (or partial specializations thereof), any errors
> within those instantiations will be hard errors.

Thanks again, Doug, for your insights. I think we can do something
about (1).

According to Daniel Kr   gler, std::is_convertible already requires
compiler magic to implement because of its definition (including
access control). The ability to check expressions for "well-
formedness" including access control is obviously desirable. If it was
possible to implement these traits in vanilla C++0x it would also
allow users to come up with their own, possibly more advanced traits
and concepts emulations which I think is very desirable.

You're saying "access control still happens after template argument
deduction" but that doesn't rule out a modified or extended decltype
primitive. In the context of argument deduction, decltype could be
restricted to public functions. Alternativly, there could be a
"pdecltype" where the initial p stands for public and is restricted to
public functions.

Such a decltype variant would allow users to come up with their own
advanced traits system that includes access checking and would also
make std::is_convertible implementable without further compiler-magic,
as far as I can tell.

Opinions?

Cheers,
SG


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





Author: SG <s.gesemann@gmail.com>
Date: Mon, 7 Sep 2009 02:21:50 CST
Raw View
I'm testing G++ 4.4.1 with respect to SFINAE and would like some
clarification on what is ill-formed and what is just considered a
decuction failure (SFINAE) in the following examples.

All the test programs are of the form

   template<typename T> T&& make();

   XXX

   template<typename T, typename = decltype( YYY )>
   int test(int) { return 1; }

   template<typename T>
   int test(...) { return 0; }

   int main() { return test<Type>(0); }

where I replaced XXX and YYY with different declarations, definitions,
and expressions.

Case 1:
  XXX:  typedef int Type;
        void foo(int*);
  YYY:  foo(make<T>())

  Compiles fine with G++ 4.4.1.
  Return code: 0 (as expected, no int&& -> int* conversion)

Case 2:
  XXX:  typedef int Type;
        void foo(int&);
  YYY:  foo(make<T>())

  Compiles fine with G++ 4.4.1.
  Return code: 0 (as expected, no int&& -> int& conversion)

Case 3:
  XXX:  typedef int Type;
        //void foo(int&);
  YYY:  foo(make<T>())

  Does NOT compile with G++ 4.4.1:
  "foo has not been declared"

Case 4:
  XXX:  typedef int Type;
        void foo(int) = delete;
  YYY:  foo(make<T>())

  Does NOT compile with G++ 4.4.1:
  "deleted function    void foo(int)    used"

Case 4:
  XXX:  struct Type {
          Type(Type const&) = delete;
          Type(Type &&);
        };
        void foo(Type);
  YYY:  foo(make<T>())

  Compiles fine with G++ 4.4.1.
  Return code: 1 (as expected, Type is move-constructible)

Case 4:
  XXX:  struct Type {
          Type(Type const&) = delete;
          Type(Type &&);
        };
        void foo(Type);
  YYY:  foo(make<T&>()) // <-- lvalue argument

  Does NOT compile with G++ 4.4.1:
  "deleted function    Type::Type(const Type&)    used"

As you can see some errors are fatal. Is this behaviour expected or is
it due to a compiler bug?

My goal here is to see whether it's possible to test whether an
arbitrary expression that successfully parses is actually valid or not
via SFINAE + decltype.

Thanks in advance,
SG


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





Author: Mathias Gaunard <loufoque@gmail.com>
Date: Tue, 8 Sep 2009 00:53:02 CST
Raw View
On 7 sep, 10:21, SG <s.gesem...@gmail.com> wrote:

> Case 3:
>   XXX:  typedef int Type;
>         //void foo(int&);
>   YYY:  foo(make<T>())
>
>   Does NOT compile with G++ 4.4.1:
>   "foo has not been declared"

That appears normal.
The compiler needs to know whether foo is a function, function object
or a type to give sense to that expression.

So you need to provide at least one declaration of foo; the arity or
the types of the arguments should be of no importance however.


> Case 4:
>   XXX:  typedef int Type;
>         void foo(int) = delete;
>   YYY:  foo(make<T>())
>
>   Does NOT compile with G++ 4.4.1:
>   "deleted function    void foo(int)    used"

> Case 4 [actually, 5]:
>   XXX:  struct Type {
>           Type(Type const&) = delete;
>           Type(Type &&);
>         };
>         void foo(Type);
>   YYY:  foo(make<T&>()) // <-- lvalue argument
>
>   Does NOT compile with G++ 4.4.1:
>   "deleted function    Type::Type(const Type&)    used"

I don't know about these, but I suspect deleted functions are taken
into account later, after template argument substitution.


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





Author: Doug Gregor <doug.gregor@gmail.com>
Date: Tue, 8 Sep 2009 12:46:23 CST
Raw View
On Sep 7, 1:21 am, SG <s.gesem...@gmail.com> wrote:
> All the test programs are of the form
>
>    template<typename T> T&& make();
>
>    XXX
>
>    template<typename T, typename = decltype( YYY )>
>    int test(int) { return 1; }
>
>    template<typename T>
>    int test(...) { return 0; }
>
>    int main() { return test<Type>(0); }
>
> where I replaced XXX and YYY with different declarations, definitions,
> and expressions.

> Case 3:
>   XXX:  typedef int Type;
>         //void foo(int&);
>   YYY:  foo(make<T>())
>
>   Does NOT compile with G++ 4.4.1:
>   "foo has not been declared"

That's a bug. "foo" is a dependent name, so when name lookup fails to
find any "foo" at template argument deduction time it's a SFINAE
error.

> Case 4:
>   XXX:  typedef int Type;
>         void foo(int) = delete;
>   YYY:  foo(make<T>())
>
>   Does NOT compile with G++ 4.4.1:
>   "deleted function    void foo(int)    used"

Definitely a bug in GCC. Use of a deleted function is meant to be a
SFINAE error.

> Case 4:
>   XXX:  struct Type {
>           Type(Type const&) = delete;
>           Type(Type &&);
>         };
>         void foo(Type);
>   YYY:  foo(make<T&>()) // <-- lvalue argument
>
>   Does NOT compile with G++ 4.4.1:
>   "deleted function    Type::Type(const Type&)    used"

Also a bug in GCC, for the same reason as in the other Case 4.

> My goal here is to see whether it's possible to test whether an
> arbitrary expression that successfully parses is actually valid or not
> via SFINAE + decltype.

You can test arbitrary expressions, but there are two major
limitations to (extended) SFINAE:

 1) Access control still happens after template argument deduction,
so you will get a hard error if your expression makes use of an
inaccessible member.

 2) If type-checking the expression causes the instantiation of any
class templates (or partial specializations thereof), any errors
within those instantiations will be hard errors.

 - Doug


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





Author: SG <s.gesemann@gmail.com>
Date: Tue, 8 Sep 2009 12:47:41 CST
Raw View
On 8 Sep., 08:53, Mathias Gaunard <loufo...@gmail.com> wrote:
> On 7 sep, 10:21, SG <s.gesem...@gmail.com> wrote:
>
> > Case 3:
> >   XXX:  typedef int Type;
> >         //void foo(int&);
> >   YYY:  foo(make<T>())
>
> >   Does NOT compile with G++ 4.4.1:
> >   "foo has not been declared"
>
> That appears normal.
> The compiler needs to know whether foo is a function, function object
> or a type to give sense to that expression.

It does not need to know about it *before* instantiation. Some
function "foo" may be found via ADL while attempting to instantiate
the template. Since T is part of that expression, lookup of "foo" is
delayed and failure to find something at instantiation-time should in
my opinion result in a deduction failure instead of a fatal error. The
above example doesn't differ much from the following one

   XXX:  struct Type {};
   YYY:  make<T>() + make<T>()

which compiles fine using G++4.4.1 and the program returns 0. It
compiles even though the compiler didn't see any "operator+"
declaration. It compiles because it's considered a deduction failure.

In case the current draft deems these cases to be fatal errors
consider the posts of mine a plea for reconsideration. With "concepts"
removed we could at least get "syntactical expression constraints" via
SFINAE+decltype.

Cheers!
SG


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





Author: Mathias Gaunard <loufoque@gmail.com>
Date: Wed, 9 Sep 2009 16:51:21 CST
Raw View
On 8 sep, 20:47, SG <s.gesem...@gmail.com> wrote:
> The
> above example doesn't differ much from the following one
>
>    XXX:  struct Type {};
>    YYY:  make<T>() + make<T>()

That differs a lot. This is parsable without semantic information. The
former is not.
Whether foo(make<T>()) is a constructor call or an operator() cannot
be deduced without a definition of foo.


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





Author: SG <s.gesemann@gmail.com>
Date: Thu, 10 Sep 2009 11:40:21 CST
Raw View
On 10 Sep., 00:51, Mathias Gaunard <loufo...@gmail.com> wrote:
> On 8 sep, 20:47, SG <s.gesem...@gmail.com> wrote:
>
> > The
> > above example doesn't differ much from the following one
>
> >    XXX:  struct Type {};
> >    YYY:  make<T>() + make<T>()
>
> That differs a lot.

I disagree on the "a lot" part.

> This is parsable without semantic information. The
> former is not.

Have you tried? The following program "parses" and compiles without
problems:

   template<typename T> T&& make();

   template<typename T, typename = decltype( foo(make<T>()) )>
   void bar(int) {}

As Doug explained, foo is a dependent name and as such lookup is
delayed. It doesn't matter what exactly it refers to. It's not
interpreted as a typeid, though, because it's not preceeded by the
typename keyword. That's all disambiguation we need. Some function
"foo" may *follow* this template definition that may be found via ADL,
for example. With respect to namelook up it's not different from

   template<typename T>
   void baz(T const& t) {
      foo(t); // foo is again a dependent name
   }

which, of course, also "parses" without any preceeding declaration of
"foo".

Cheers!
SG


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





Author: Doug Gregor <doug.gregor@gmail.com>
Date: Fri, 11 Sep 2009 13:58:35 CST
Raw View
On Sep 10, 10:40 am, SG <s.gesem...@gmail.com> wrote:
> On 10 Sep., 00:51, Mathias Gaunard <loufo...@gmail.com> wrote:
>
> > On 8 sep, 20:47, SG <s.gesem...@gmail.com> wrote:
>
> > > The
> > > above example doesn't differ much from the following one
>
> > >    XXX:  struct Type {};
> > >    YYY:  make<T>() + make<T>()
>
> > That differs a lot.
>
> I disagree on the "a lot" part.
>
> > This is parsable without semantic information. The
> > former is not.
>
> Have you tried? The following program "parses" and compiles without
> problems:
>
>    template<typename T> T&& make();
>
>    template<typename T, typename = decltype( foo(make<T>()) )>
>    void bar(int) {}
>
> As Doug explained, foo is a dependent name and as such lookup is
> delayed.

Right. For reference, dependent names are covered in section 14.6.2
[temp.dep] of the C++ standard. Paragraph 1 covers the case under
discussion.

 - Doug


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