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

Template argument deduction mystery

45 views
Skip to first unread message

Juha Nieminen

unread,
Sep 4, 2019, 9:55:33 AM9/4/19
to
In C++17 you can do this:

//-----------------------------------------------------
#include <iostream>
#include <algorithm>
#include <functional>

int main(int argc, char** argv)
{
double values[5] = { 1.1, 2.2, 3.3, 4.4, 5.5 };

// Notice this:
std::sort(values, values+5, std::greater());

for(double v: values) std::cout << v << " ";
std::cout << "\n";
}
//-----------------------------------------------------

You don't actually need to specify the template parameter of std::greater.
It will be automatically deduced as 'double'. That's really nice.

But wait... How?!?

There's nothing telling std::greater that the template parameter should be
of type double. It's not taking any constructor parameters or anything.
And the declaration of std::sort() is ostensibly like this:

template<class RandomIt, class Compare>
void sort(RandomIt first, RandomIt last, Compare comp);

so there's nothing there telling it that it should be double either.

So how?

Ian Collins

unread,
Sep 4, 2019, 3:16:50 PM9/4/19
to
Magic? I assume it's related to the changes in C++17 which extend
template argument deduction to template classes as well as functions.

All of the information is there, given the signature of Compare is bool
comp(const Type1 &a, const Type2 &b) and Type1 and Type2 can be deduced
from RandomIt.

--
Ian.

Bonita Montero

unread,
Sep 4, 2019, 9:03:17 PM9/4/19
to
> std::sort(values, values+5, std::greater());

Doesn't work for me.
As I excpected VC++ as well as g++ require a template parameter.

Juha Nieminen

unread,
Sep 5, 2019, 5:50:22 PM9/5/19
to
It appears that gcc has been slow to implement automatic template
parameter deduction of this caliber. It only works from gcc 9
forwards. It doesn't work on previous versions of gcc.

It does work on the current version of clang (I haven't tested
which is the first version that added support).

As for the answer to the question, I think I understand it now.

std::greater is currently declared as

template<typename T=void>
struct greater;

This means that the C++17 automatic template parameter deduction
will make it std::greater<void> when no parameter is specified.

std::greater<void> in turn has a specialization that has a
templated operator(), which allows it to compare any types.

Pavel

unread,
Sep 5, 2019, 8:04:48 PM9/5/19
to
Uh-huh. Just in case you have not found it these are called "deduction
guides" in the standard. I did not answer you question because I still
don't understand these enough to use fearlessly. A good tutorial /
how-to article would be useful. -Pavel

Chris Vine

unread,
Sep 6, 2019, 9:55:19 AM9/6/19
to
'template <typename T = void> struct greater' (and its equivalent for
the other standard comparison structs) was first provided in C++14
(not C++17), allowing type deduction for operator(). It does not need
to rely on C++17 deduction guides - you can supply an empty template
type specifier of <> when instantiating std::greater in C++14.

Pavel

unread,
Sep 6, 2019, 11:56:39 PM9/6/19
to
OP asked how std::greater() works (i.e. w/o <>). I think for this to
work, guides are needed, no? -Pavel

Chris Vine

unread,
Sep 7, 2019, 10:25:54 AM9/7/19
to
> > (not C++17), allowing type deduction for operator(). It does not neeld
> > to rely on C++17 deduction guides - you can supply an empty template
> > type specifier of <> when instantiating std::greater in C++14.
>
> work, guides are needed, no? -Pavel

As I read it, he asked about the specialization of std::greater<void>
which allows type deduction in its call operator. That was made
available in C++14.

In any event, although I am willing to be corrected I thought
deduction guides were only relevant to constructors, and the
constructor of std::greater takes no arguments from which types can be
deduced. It is the call operator which carries out type deduction
where the std::greater type is instantiated (explicitly or implicitly)
for the void type. C++17 happens (as I understand it) to allow the <>
to be omitted where the object is instantiated for the default type.

Juha Nieminen

unread,
Sep 7, 2019, 2:56:08 PM9/7/19
to
Chris Vine <chris@cvine--nospam--.freeserve.co.uk> wrote:
> As I read it, he asked about the specialization of std::greater<void>
> which allows type deduction in its call operator. That was made
> available in C++14.

I was, in fact, asking about both things.

It was completely mysterious to me how exactly std::greater would
know that it was supposed to be comparing doubles, when nowhere in
its instantiation it's told that. I didn't know that it has a
specialization for void that works for exactly this kind of situation.

It's also a bit unclear to me how it works without the empty <>
brackets. I know it's related to C++ automatic template type
deduction, but it's unclear to me how it works in this case.
Curiously, it appears that it works without any explicit deduction
guides. It works for the mere reason that the default template
parameter for std::greater is void.

The rules of automatic template parameter type deduction are still
a bit unclear to me.

Bo Persson

unread,
Sep 7, 2019, 3:28:42 PM9/7/19
to
The possiblity of omitting an empty <> is just mentioned in passing,
while decribing "Explicit template argument specification":

"Trailing template arguments that can be deduced or obtained from
default template-arguments may be omitted from the list of explicit
template-arguments. A trailing template parameter pack ([temp.variadic])
not otherwise deduced will be deduced as an empty sequence of template
arguments. If all of the template arguments can be deduced, they may all
be omitted; in this case, **the empty template argument list <> itself
may also be omitted.**"

http://eel.is/c++draft/temp.arg.explicit#4


Not obvious at all, if you happened to skip over the last part of this
sentence. :-)


Bo Persson



Chris Vine

unread,
Sep 7, 2019, 6:06:58 PM9/7/19
to
For instantiations of std::greater for other than the void type, the
call operator takes arguments of the type for which the struct has been
instantiated. In particular, std::greater is a class template, but the
call operator is not a function template so no type deduction takes
place.

std::greater<void> is a specialization of std::greater. In this
specialization, the call operator is a function template. So it does
deduce the type.

If this is still confusing note that:

template <class T> struct A { // class template
void do_it(T) {...} // not a function template, no deduction
};

struct B {
template <class T>
void do_it(T) {...} // function template, argument types deduced
};

Pavel

unread,
Sep 7, 2019, 6:36:03 PM9/7/19
to
Thanks, good to know; always glad to stand corrected. As I mentioned, I
did not yet understand the guides enough so I guess I was ascribing all
superpowers to them. Turned out, the explanation was much simpler: the
syntax with <> could even be implemented at C++11 level if defined for
the library. -Pavel

Ian Collins

unread,
Sep 7, 2019, 8:29:55 PM9/7/19
to
Thanks for filling in the gaps and the link!

I glad I'm not a compiler writer... gcc-9 gets it right, gcc-8 gets it
wrong.

--
Ian.
0 new messages