3

I came up with this after answering this question

I had a simple function template (C++11):

template<class elem_t, class list_t>
bool in_list(const elem_t& elem, const list_t& list) {
   for (const auto& i : list) {
      if (elem == i) {
         return true;
      }
   }
   return false;
}

But GCC emitted warnings because it doesn't seem to like deducing a template parameter as a std::initializer_list. So, without thinking, I made a specialization:

template<class elem_t>
bool in_list(const elem_t& elem, std::initializer_list<elem_t> list) {
   for (const auto& i : list) {
      if (elem == i) {
         return true;
      }
   }
   return false;
}

This worked. No more warnings. But when I looked again and thought about it, I remembered that C++ does not support partial template specialization on function templates. But that is what this appears to be. My only guess is that this is allowed because std::initializer_list is still dependent upon the template parameter, so it is, in essence, a different template. But I'm not sure if this is how it is supposed to be (isn't there a gotw about templates not overloading?).

Is it standard behavior to accept this? And why?

And as a bonus question, why does GCC not like deducing a template parameter as a std::initializer_list? It seems quite silly to expect me to copy and paste the code and just replace the parameter with a std::initializer_list.

The warning message:

test.cpp: In function ‘int main()’:
test.cpp:33:43: warning: deducing ‘const list_t’ as ‘const std::initializer_list<int>’ [enabled by default]
test.cpp:6:6: warning:   in call to ‘bool in_list(const elem_t&, const list_t&) [with elem_t = int, list_t = std::initializer_list<int>]’ [enabled by default]
test.cpp:33:43: warning:   (you can disable this with -fno-deduce-init-list) [enabled by default]

When called by in_list(3, {1, 2, 3, 4, 5});

EDIT: Apparently deducing a template parameter as an initializer_list is an extension according to the working draft for my version of GCC (cite). So new question: Is this still an extension as of the final c++11 standard? If so, this would mean that it would be necessary for me to add the second function for standards-compliant code. Thanks for all your help!

EDIT2: The compiler dialect flag appears to be removed for GCC 4.7, so it seems like the issue was resolved, but I don't know how it was resolved.

Community
  • 1
  • 1
Robert Mason
  • 3,949
  • 3
  • 31
  • 44
  • 1
    No, it's not template specialisation, and you can tell just by looking at the syntax. What is it? Overloading I think, but I'll let someone more knowledgeable answer. – Seth Carnegie Apr 29 '12 at 17:26
  • I'm not exactly sure which. I'm not sure. Since it forms a subset but is still a template, I would say it is (at least semantically) a specialization. But syntactically it looks more like overloading. But I'm not sure exactly how it overloads or what the standard says. – Robert Mason Apr 29 '12 at 17:30
  • I'm looking through the standard now and collecting some info that might help. – Seth Carnegie Apr 29 '12 at 17:55
  • 2
    Ack, it's too long and complicated. – Seth Carnegie Apr 29 '12 at 18:04
  • @Robert, I suggest taking the initializer_list as an rvalue-reference, to avoid copies. – Mooing Duck Apr 29 '12 at 18:18
  • @MooingDuck: Good idea. Anyone know why GCC doesn't like deducing a template argument as std::initializer_list? Why does it issue a warning for this? – Robert Mason Apr 29 '12 at 18:21
  • wait, I thought you said the warning was for taking the initializer_list by const reference, not for deducing a template argument. – Mooing Duck Apr 29 '12 at 18:24
  • I did. I was wrong. Sorry, I'll edit the question. – Robert Mason Apr 29 '12 at 18:25
  • What exactly does GCC say when it doesn't like deducing that template argument? – Seth Carnegie Apr 29 '12 at 18:29
  • I posted the warning. It works fine, so I can safely ignore it, but I never like just ignoring warnings as they often hint at a deeper problem. If GCC feels the need to warn me about doing something it's probably because I don't know what I'm doing :D – Robert Mason Apr 29 '12 at 18:34
  • Yes, apparently you do need the second overload for standards-compliant code: http://stackoverflow.com/a/4763493/726361 – Seth Carnegie Apr 29 '12 at 18:42
  • for your initializer list deduction question, also see http://stackoverflow.com/q/7699963/34509 – Johannes Schaub - litb May 01 '12 at 18:06

2 Answers2

4

Using what @Ben Voigt said in the comments on the other answer, I have gathered some relevant standard quotes:

§14.5.6.2

A function template can be overloaded with other function templates and with normal (non-template) functions. A normal function is not related to a function template (i.e., it is never considered to be a specialization), even if it has the same name and type as a potentially generated function template specialization.

So that rules out function template specialisation as what you're doing, because even if two function template overloads could potentially generate the same function, it's not specialisation. So it's overloading.

Such specializations are distinct functions and do not violate the one definition rule (3.2).

So they're distinct functions and that's why it's not erroring.

§14.5.6.2.1

If a function template is overloaded, the use of a function template specialization* might be ambiguous because template argument deduction (14.8.2) may associate the function template specialization with more than one function template declaration.

This is priming is for what we both already saw, which is that in_list(a, b) where b is an initializer_list appears to match both function templates.

(*Note that "function template specialization" here doesn't mean specialising a function template, it means a function template that has been instantiated with a type. So with template<typename T> f();, f<int>() is a function template specialisation.)

So we use what is called partial ordering of overloaded function templates to resolve this:

Partial ordering of overloaded function template declarations is used in the following contexts to select the function template to which a function template specialization refers:

— during overload resolution for a call to a function template specialization (13.3.3);

— when the address of a function template specialization is taken;

— when a placement operator delete that is a function template specialization is selected to match a placement operator new (3.7.4.2, 5.3.4);

— when a friend function declaration (14.5.4), an explicit instantiation (14.7.2) or an explicit specialization (14.7.3) refers to a function template specialization.

Ok, so that's when partial ordering is for. This is what it does:

Partial ordering selects which of two function templates is more specialized than the other by transforming each template in turn (see next paragraph) and performing template argument deduction using the function type. The deduction process determines whether one of the templates is more specialized than the other. If so, the more specialized template is the one chosen by the partial ordering process.

And then you get into the long and laborious process of determining which template is more specialised, which you can read about if you want, but it's really complicated and I probably don't understand it all (and plus, I don't have enough time to write about it :)).

Community
  • 1
  • 1
Seth Carnegie
  • 73,875
  • 22
  • 181
  • 249
  • So if I changed the declaration of the second snippet to template bool in_list>(const elem_t& elem, std::initializer_list list) then it would be illegal? And that since it is legal, the C++ standard has the compiler follow arcane rules that determine that the second is more specific. So, in essence, it works by magic? :D – Robert Mason Apr 29 '12 at 18:29
  • (referencing http://stackoverflow.com/questions/5101516/why-function-template-cannot-be-partially-specialized) – Robert Mason Apr 29 '12 at 18:30
  • @RobertMason yes, that would be illegal, because _partial_ template specialisation is illegal. And yes, the C++ standard has the compiler follow long rules about which one is more specialised and therefore which one to "select" to be used. So yeah, magic :) – Seth Carnegie Apr 29 '12 at 18:31
3

That's not partial specilization. What you are doing is overloading function.

dchhetri
  • 6,926
  • 4
  • 43
  • 56
  • 1
    `in_list(a, b)` when `b` is an initialiser list seems to match both templates. What are the rules for looking up overloads on templates like this? – Seth Carnegie Apr 29 '12 at 17:27
  • 2
    @Seth: The more specific overload, using `initializer_list`, will be preferred. The rules are UGLY, and I doubt there are more than ten people in the world (I'm not one) who actually understand them *completely*. – Ben Voigt Apr 29 '12 at 17:33
  • 2
    @Seth: sections 13.3, 14.5.6.1, 14.5.6.2, 14.8.2.4, 14.8.3 I'm pretty sure there's an existing question for which one of the answers includes a translation into almost-understandable. – Ben Voigt Apr 29 '12 at 17:36