Function templates, non-deduced contexts, and empty parameter packs

349 views
Skip to first unread message

barry....@gmail.com

unread,
Nov 22, 2016, 9:37:16 AM11/22/16
to ISO C++ Standard - Discussion
This example is apparently ill-formed:

#include <iostream>


template <class... Ts>
struct Y
{
   
static_assert(sizeof...(Ts)>0, "!");
};


template <class... Ts>
std
::ostream& operator<<(std::ostream& os, Y<Ts...> const& )
{
   
return os << std::endl;
}


int main()
{
}

endl isn't dependent, so when we perform overload resolution, our new operator<< template is a candidate. But endl is a function template, which makes that parameter a non-deduced context, which ends up yielding an empty parameter pack for Ts..., which triggers the static_assert

This seems very non-intuitive from a language perspective, since the Y class template is not in any way related to endl. Is this really the desired behavior in this situation?

Columbo

unread,
Nov 22, 2016, 4:05:52 PM11/22/16
to ISO C++ Standard - Discussion, barry....@gmail.com
I think the fundamental problem may lie within the way deduction is performed for non-deducible packs.

template <class... Ts>
struct Y {};


template <class... Ts>
void f(Y<Ts...>) {}


int main() {
    f
({});
}

Do we really want this to work? If not, we could adjust (the completely misplaced) [temp.arg.explicit]/3 to not deduce Ts at all if it only appears in parameters whose arguments make them non-deduced. 

Johannes Schaub

unread,
Nov 22, 2016, 5:07:37 PM11/22/16
to std-dis...@isocpp.org
Basing this on "arguments [that] make them non-deduced" seems a bad
idea. The fact that in your and in the original example the parameter
is a non-deduced context is not representative for this problem at
large, I think. If "std::endl" had been an object or a non-overloaded
function, I would still not want "Y<Ts...>" make "Ts" fallback to be
empty. Why behave different for "Ts..." and "Ts" here?

I would perhaps say that, only if every appearance of Ts is in
non-deduced contexts or in parameters that don't participate in
deduction, it may fallback to being empty.
> --
>
> ---
> You received this message because you are subscribed to the Google Groups
> "ISO C++ Standard - Discussion" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to std-discussio...@isocpp.org.
> To post to this group, send email to std-dis...@isocpp.org.
> Visit this group at
> https://groups.google.com/a/isocpp.org/group/std-discussion/.

Johannes Schaub

unread,
Nov 22, 2016, 5:12:08 PM11/22/16
to std-dis...@isocpp.org
And, we need to be careful in considering "f<>({})", which I suppose
we should keep well-formed. As far as I'm aware, there is no wording
that explicitly attributes the empty template argument list (here and
at trailing positions with preceeding other template arguments for
other parameters) as expressing that the corresponding template
parameter pack would be empty. Right now, it simply means to not feed
anything into the template parameter pack.

Richard Smith

unread,
Nov 22, 2016, 5:32:10 PM11/22/16
to std-dis...@isocpp.org
On 22 November 2016 at 14:07, 'Johannes Schaub' via ISO C++ Standard - Discussion <std-dis...@isocpp.org> wrote:
Basing this on "arguments [that] make them non-deduced" seems a bad
idea. The fact that in your and in the original example the parameter
is a non-deduced context is not representative for this problem at
large, I think. If "std::endl" had been an object or a non-overloaded
function, I would still not want "Y<Ts...>" make "Ts" fallback to be
empty. Why behave different for "Ts..." and "Ts" here?

I would perhaps say that, only if every appearance of Ts is in
non-deduced contexts or in parameters that don't participate in
deduction, it may fallback to being empty.

That sounds right to me.
 

> To post to this group, send email to std-dis...@isocpp.org.
> Visit this group at
> https://groups.google.com/a/isocpp.org/group/std-discussion/.

--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+unsubscribe@isocpp.org.

T. C.

unread,
Nov 22, 2016, 5:38:26 PM11/22/16
to ISO C++ Standard - Discussion


On Tuesday, November 22, 2016 at 5:07:37 PM UTC-5, Johannes Schaub wrote:
Basing this on "arguments [that] make them non-deduced" seems a bad
idea. The fact that in your and in the original example the parameter
is a non-deduced context is not representative for this problem at
large, I think. If "std::endl" had been an object or a non-overloaded
function, I would still not want "Y<Ts...>" make "Ts" fallback to be
empty. Why behave different for "Ts..." and "Ts" here?


If "std::endl" had been an object or a non-overloaded function, you get a deduction failure right there, not a non-deduced context, so there's no "fallback to empty".

Johannes Schaub

unread,
Nov 22, 2016, 5:55:26 PM11/22/16
to std-dis...@isocpp.org
Hmm, missed that. It appears you are right.

Columbo

unread,
Nov 24, 2016, 3:41:36 PM11/24/16
to ISO C++ Standard - Discussion
On Tuesday, November 22, 2016 at 10:12:08 PM UTC, Johannes Schaub wrote:
And, we need to be careful in considering "f<>({})", which I suppose we should keep well-formed.

Making f({}) and f<>({}) behave differently wrt to deduction is very unsound, IMO. I am currently drafting a paper on free packs and pack literals, under which you could write f<{}>(...) to express that the pack is empty. I would personally prefer that to f<>(...).

On Tuesday, November 22, 2016 at 10:07:37 PM UTC, Johannes Schaub wrote:
I would perhaps say that, only if every appearance of Ts is in non-deduced contexts or in parameters that don't participate in deduction, it may fallback to being empty.

I think you have to be more careful with terminology, because as I interpret the above, you're saying that Barry's original example should fail--after all, std::endl as an argument is a non-deduced context as per [temp.deduct.type]/(5.5)

If you meant that the function parameter type must already disallow any deduction for the pack right away, i.e. bullets 1, 2, 3, 4 and 7 in [temp.deduct.type]/5, then the condition appears to be inconsistent with [temp.deduct.call]/1, which mandates (via fallback to [temp.arg.explicit]/3--cf. Bogdan's comment here) that when the pack appears in a "proper" non deduced context, it is deduced as empty. 

template <typename... T>
void f(tuple<T...>);


f
({}); // T... appears in a deduced "type" context in tuple<T...>, so you're saying this should fail

So now your condition is breaking code. I probably misunderstood what you meant, could you elaborate?

Columbo

unread,
Nov 24, 2016, 3:44:26 PM11/24/16
to ISO C++ Standard - Discussion


On Thursday, November 24, 2016 at 8:41:36 PM UTC, Columbo wrote:
On Tuesday, November 22, 2016 at 10:12:08 PM UTC, Johannes Schaub wrote:
And, we need to be careful in considering "f<>({})", which I suppose we should keep well-formed.

Making f({}) and f<>({}) behave differently wrt to deduction is very unsound, IMO. I am currently drafting a paper on free packs and pack literals, under which you could write f<{}>(...) to express that the pack is empty. I would personally prefer that to f<>(...).

On Tuesday, November 22, 2016 at 10:07:37 PM UTC, Johannes Schaub wrote:
I would perhaps say that, only if every appearance of Ts is in non-deduced contexts or in parameters that don't participate in deduction, it may fallback to being empty.

I think you have to be more careful with terminology, because as I interpret the above, you're saying that Barry's original example should fail--after all, std::endl as an argument is a non-deduced context as per [temp.deduct.type]/(5.5)

If you meant that the function parameter type must already disallow any deduction for the pack right away, i.e. bullets 1, 2, 3, 4 and 7 in [temp.deduct.type]/5, then the condition appears to be inconsistent with [temp.deduct.call]/1, which mandates (via fallback to [temp.arg.explicit]/3--cf. Bogdan's comment here) that when the pack appears in a "proper" non deduced context, it is deduced as empty. 

template <typename... T>
void f(tuple<T...>);


f
({}); // T... appears in a deduced "type" context in tuple<T...>, so you're saying this should fail


Sorry, that's of course what we want to break under the better rules; is this what you meant, then?

Johannes Schaub

unread,
Nov 24, 2016, 4:12:49 PM11/24/16
to std-dis...@isocpp.org
I guess I don't remember anymore what I meant with "only if every
appearance of Ts is in non-deduced contexts" - probably It was your
interpretation, but I don't anymore see why that would be beneficial.
On second thought, I probably want to cut it down to "If every
appearance of Ts is in parameters that don't participate in deduction"
which would cover cases that have no correspondign call argument, such
as the below case, or one that makes use of a default argument like
"tuple<T...> t = tuple<T...>()"

template<typename ...T>
void f(T ...);
f(); // "T..." does not participate in deduction

However I like your solution better, but note that in both your and my
solution, if we say "tuple<T...> t = {}", I.e. have a default brace
argument, it will accept. But if we explicitly pass a brace argument,
it will reject because the pack appears in a parameter that
participates in deduction (and the parameter is a non-deduced context
during deduction nontheless). That seems weirdly inconsistent to me.
Usually, I think of default arguments to weaken the ability to deduce
things compared to explicitly passing the same argument, not the other
way around.

barry....@gmail.com

unread,
Nov 29, 2016, 9:59:17 AM11/29/16
to ISO C++ Standard - Discussion


On Thursday, November 24, 2016 at 2:41:36 PM UTC-6, Columbo wrote:
On Tuesday, November 22, 2016 at 10:12:08 PM UTC, Johannes Schaub wrote:
And, we need to be careful in considering "f<>({})", which I suppose we should keep well-formed.

Making f({}) and f<>({}) behave differently wrt to deduction is very unsound, IMO. I am currently drafting a paper on free packs and pack literals, under which you could write f<{}>(...) to express that the pack is empty. I would personally prefer that to f<>(...).

On Tuesday, November 22, 2016 at 10:07:37 PM UTC, Johannes Schaub wrote:
I would perhaps say that, only if every appearance of Ts is in non-deduced contexts or in parameters that don't participate in deduction, it may fallback to being empty.

I think you have to be more careful with terminology, because as I interpret the above, you're saying that Barry's original example should fail--after all, std::endl as an argument is a non-deduced context as per [temp.deduct.type]/(5.5)

If you meant that the function parameter type must already disallow any deduction for the pack right away, i.e. bullets 1, 2, 3, 4 and 7 in [temp.deduct.type]/5, then the condition appears to be inconsistent with [temp.deduct.call]/1, which mandates (via fallback to [temp.arg.explicit]/3--cf. Bogdan's comment here) that when the pack appears in a "proper" non deduced context, it is deduced as empty. 

template <typename... T>
void f(tuple<T...>);


f
({}); // T... appears in a deduced "type" context in tuple<T...>, so you're saying this should fail

So now your condition is breaking code. I probably misunderstood what you meant, could you elaborate?

I haven't done a survey or anything, but I think most people would be pretty surprised by the fact that this is well-formed code that calls f(tuple<> ).  That just seems so wrong. 

Robert Haberlach

unread,
Nov 29, 2016, 12:00:25 PM11/29/16
to std-dis...@isocpp.org


On 11/29/2016 02:59 PM, barry....@gmail.com wrote:
>
>
> On Thursday, November 24, 2016 at 2:41:36 PM UTC-6, Columbo wrote:
>
> On Tuesday, November 22, 2016 at 10:12:08 PM UTC, Johannes Schaub wrote:
>
> And, we need to be careful in considering "f<>({})", which I suppose we should keep well-formed.
>
>
> Making f({}) and f<>({}) behave differently wrt to deduction is very unsound, IMO. I am currently drafting a paper on free packs and pack
> literals, under which you could write f<{}>(...)to express that the pack is empty. I would personally prefer that to f<>(...).
>
> On Tuesday, November 22, 2016 at 10:07:37 PM UTC, Johannes Schaub wrote:
>
> I would perhaps say that, only if every appearance of Ts is in non-deduced contexts or in parameters that don't participate in deduction, it
> may fallback to being empty.
>
>
> I think you have to be more careful with terminology, because as I interpret the above, you're saying that Barry's original example should
> fail--after all, std::endl as an argument is a non-deduced context as per [temp.deduct.type]/(5.5) <http://eel.is/c++draft/temp.deduct.type#5.5>.
>
> If you meant that the function parameter type must already disallow any deduction for the pack right away, i.e. bullets 1, 2, 3, 4 and 7 in
> [temp.deduct.type]/5, then the condition appears to be inconsistent with [temp.deduct.call]/1 <http://eel.is/c++draft/temp.deduct#call-1>, which
> mandates (via fallback to [temp.arg.explicit]/3 <http://eel.is/c++draft/temp.arg.explicit#3>--cf. Bogdan's comment here
> <https://llvm.org/bugs/show_bug.cgi?id=21774#c2>) that when the pack appears in a "proper"/ /non deduced context, it is deduced as empty.
>
> |
> template<typename...T>
> voidf(tuple<T...>);
>
>
> f({});// T... appears in a deduced "type" context in tuple<T...>, so you're saying this should fail
> |
>
> So now your condition is breaking code. I probably misunderstood what you meant, could you elaborate?
>
>
> I haven't done a survey or anything, but I think most people would be pretty surprised by the fact that this is well-formed code that calls f(tuple<>
> ). That just seems so wrong.

My word--see my forwarded message. :-)

>
> --
>
> ---
> You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to std-discussio...@isocpp.org
> <mailto:std-discussio...@isocpp.org>.
> To post to this group, send email to std-dis...@isocpp.org <mailto:std-dis...@isocpp.org>.
Reply all
Reply to author
Forward
0 new messages