18

In a related question it's said that there's no such thing as a pointer to non-member const function. In addition, C++11 8.3.5/6 says

The effect of a cv-qualifier-seq in a function declarator is not the same as adding cv-qualification on top of the function type. In the latter case, the cv-qualifiers are ignored. [ Note: a function type that has a cv-qualifier-seq is not a cv-qualified type; there are no cv-qualified function types. —end note ]

If I understand it correctly, this means that there's no such thing as a non-member const function. (Although such functions are not const, they cannot be modified as per 3.10/6). In particular, pointers to const function are meaningless.

However, it seems that some compilers do create pointers to const function in type deduction contexts. For instance, consider the code:

#include <iostream>

void f() {}

template <typename T> void g(      T*) { std::cout << "non const" << std::endl; }
template <typename T> void g(const T*) { std::cout << "const    " << std::endl; }

int main() {
     g(f);
}

When compiled with GCC and Intel the code outputs "non const" as I would expect from the quote above. However, the output is "const" when compiled with Clang and Visual Studio.

Is my interpretation correct?

Update:

Following the comments, I'm trying to clarify that I'm not talking about const member functions. I'm interested in non-member functions (but the same arguments probably apply to non-static member functions as well). I've also changed the question title to make it more precise.

Consistent with the resolution of g(f) mentioned above, the line below is ilegal for GCC and Intel but not for Clang and Visual Studio

const auto* ptr = &f;

Update 2:

I agree with Andy Prowl's interpretation and have selected his answer. However, after that, I was made aware that this question is a CWG open issue.

Community
  • 1
  • 1
Cassio Neri
  • 19,583
  • 7
  • 46
  • 68
  • For starters, this is not "a *cv-qualifier-seq* in a function declarator". This is in a function-pointer declarator. – Ben Voigt Mar 22 '13 at 19:25
  • 2
    [Related](http://stackoverflow.com/questions/1117873/pointer-to-const-vs-usual-pointer-for-functions) –  Mar 22 '13 at 19:30
  • 1
    I would interpret your title as you're looking for uniquely identifying a `void g(T*) const;` signature of a class member. The question is different though .. – πάντα ῥεῖ Mar 22 '13 at 19:35
  • 1
    no, your interpretation is just silly. you're quoting the relevant standardese. it tells you that "The effect of a cv-qualifier-seq in a function declarator is not the same as adding cv-qualification on top of the function type", what on eart his unclear about that – Cheers and hth. - Alf Mar 22 '13 at 19:50
  • @Cheersandhth.-Alf: Hope that "helps" indeed – Lightness Races in Orbit Mar 22 '13 at 20:02
  • 1
    @LightnessRacesinOrbit: oh well, i was wrong: the question **does make sense**. i need coffee. i think the call should be ambiguous but both cl and g++ accept it (with diffent overload resolutions). – Cheers and hth. - Alf Mar 22 '13 at 20:12
  • 1
    +1 for pointing out compiler difference. – Cheers and hth. - Alf Mar 22 '13 at 20:13
  • just to clear things up, the question is really: is the noted different compiler behavior somehow allowed, e.g. by non-diagnosable something something, or should the call (as I think) be diagnosed as ambiguous? – Cheers and hth. - Alf Mar 22 '13 at 20:17

1 Answers1

10

If I understand it correctly, this means that there's no such thing as a non-member const function. (Although such functions are not const, they cannot be modified as per 3.10/6). In particular, pointers to const function are meaningless.

Yes, there is no such a thing as a const function, and attempts to create one are ignored, because of the very same Paragraph you quoted. This is important, because a program that anyhow creates a const function type is not ill-formed (as was the case in C++03); simply, its attempt is disregarded, and a non-const function type is considered instead.

This is probably what GCC and ICC fail to apply, because when the non-const overload is removed, the program does not compile:

#include <iostream>

void f() {}

template <typename T> void g( T const*) 
{ 
   std::cout << "const    " << std::endl; 
}

int main() {
     g(f); // ERROR with GCC and ICC, compiles with Clang
}

Concerning your interpretation:

When compiled with GCC and Intel the code outputs "non const" as I would expect from the quote above. However, the output is "const" when compiled with Clang and Visual Studio. Is my interpretation correct?

I don't believe so. As far as I can tell, Clang is right.

This conclusion is based on the fact that both function templates are viable, because const qualifiers are ignored on function types, and one is more specialized than the other.

Per Paragraph 8.3.5/7 of the C++11 Standard, in fact:

[...] The effect of a cv-qualifier-seq in a function declarator is not the same as adding cv-qualification on top of the function type. In the latter case, the cv-qualifiers are ignored. [...]

This effectively makes the second function template viable for resolving the call (the first one obviously is). But since both function templates are viable, Paragraph 13.3.3.1 about overload resolution comes into play:

Given these definitions, a viable function F1 is defined to be a better function than another viable function F2 if for all arguments i, ICSi(F1) is not a worse conversion sequence than ICSi(F2), and then

— for some argument j, ICSj(F1) is a better conversion sequence than ICSj(F2), or, if not that,

— the context is an initialization by user-defined conversion (see 8.5, 13.3.1.5, and 13.3.1.6) and the standard conversion sequence from the return type of F1 to the destination type (i.e., the type of the entity being initialized) is a better conversion sequence than the standard conversion sequence from the return type of F2 to the destination type. [ ... ] or, if not that,

— F1 is a non-template function and F2 is a function template specialization, or, if not that,

F1 and F2 are function template specializations, and the function template for F1 is more specialized than the template for F2 according to the partial ordering rules described in 14.5.6.2.

Since the second function template is more specialized than the first one, the second function template should be picked by overload resolution. Therefore, Clang is right.

Community
  • 1
  • 1
Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • +1 for thinking about "more specialized" irregardless of type – Cheers and hth. - Alf Mar 22 '13 at 22:02
  • Yes. I agree with your interpretation. Thanks for your help. – Cassio Neri Mar 22 '13 at 22:04
  • Hmm, I'm still wondering if template argument deduction should not fail with `const T*`. – Jesse Good Mar 22 '13 at 22:12
  • @JesseGood: I don't think it should, because `const` is effectively ignored for function types. And even if it was not during type deduction, I believe 14.8.2.1/4 would help: *"The transformed A can be another pointer or pointer to member type that can be converted to the deduced A via a qualification conversion (4.4)."* – Andy Prowl Mar 22 '13 at 22:14
  • 1
    As I said, I agree with your interpretation but, actually, I've just learned that this is an [open issue](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1584). – Cassio Neri Mar 23 '13 at 12:54
  • @CassioNeri: OK, IMHO the Standard is clear about it, but obviously somebody has reasons for disagreeing. Thank you for the link – Andy Prowl Mar 23 '13 at 13:01
  • "This is probably what GCC and ICC fail to apply". If you say `g(f)`, they *do* accept the call. So the error is not that they don't apply it, but that argument deduction is different with them (and it is the already mentioned defect report that will have to clarify the matters here). – Johannes Schaub - litb Mar 27 '13 at 20:13
  • I also disagree about the qualification conversion fallback, because a qualification conversion is defined by *"A prvalue of type "pointer to cv1 T" can be converted to a prvalue of type "pointer to cv2 T" if "cv2 T" is more cv-qualified than "cv1 T"."*. However `const FunctionType` is never more cv-qualified than `FunctionType`, so no qualification conversion can be done. – Johannes Schaub - litb Mar 27 '13 at 20:20
  • If you argue that the fallback isn't even necessary, then with the same argument you can say that `const T` matches an `U&`, because `const ReferenceType` stays `ReferenceType`. But if you try, no compiler does so: All reject `template struct A; template struct A {}; A a;`. – Johannes Schaub - litb Mar 27 '13 at 20:24
  • @Johannes: Concerning argument deduction, it seems to me that 8.3.5/7 is quite clear: any attempt to append a cv qualification to a function type should be ignored, be it during type deduction or not. This is just my opinion though. – Andy Prowl Mar 27 '13 at 20:25
  • (and you also need to make `T&&` match `U&`, because substituting `U&` for `T` yields `U&` again). So it is clear that argument deduction can't work by "try to substitute and see whether things match up". – Johannes Schaub - litb Mar 27 '13 at 20:29
  • @Johannes: If I understand your argument correctly, then yes, I think qualification conversion isn't necessary. I am not sure I understand your analogy with references, but it seems that [your example compiles](http://liveworkspace.org/code/3ji7qM$16). Am I missing something? – Andy Prowl Mar 27 '13 at 20:31
  • @AndyProwl your paste is not identical to my testcase :) – Johannes Schaub - litb Mar 27 '13 at 20:31
  • @JohannesSchaub-litb: I see, I added the braces. Well, does the Standard also specify explicitly that cv qualifications on reference types should be ignored? I mean, the equivalent of 8.3.5/7 for reference types – Andy Prowl Mar 27 '13 at 20:33
  • @AndyProwl yes, 8.3.2p1. Pretty prominently :) – Johannes Schaub - litb Mar 27 '13 at 20:37
  • @JohannesSchaub-litb: OK, thank you for that :) But then what I understand from it is that the correct behavior would be to have your test case compiling, consistently with what I would expect in the case of cv-qualified function types. Is this thinking incorrect? – Andy Prowl Mar 27 '13 at 20:41
  • @AndyProwl assuming your interpretation, I think that would need to be the correct behavior. And IMO consequently that means that `T&&` needs to match `int&` aswell (as described above). Since that must not happen, the whole argument falls apart or at least has a several flaw. – Johannes Schaub - litb Mar 27 '13 at 20:46
  • @JohannesSchaub-litb: I know this will reveal my lack of knowledge, but well, I just want to understand. I can't help following the logic you are outlining and indeed deduce that `T&&` needs to match `int&`. Why that must not happen? – Andy Prowl Mar 27 '13 at 20:57
  • @AndyProwl it would not be too helpful to have `template struct is_rvalue_reference : false_type {}; template struct is_rvalue_reference : true_type{};` say "yes" for `int&`. – Johannes Schaub - litb Mar 27 '13 at 21:02
  • @JohannesSchaub-litb: My instinct is to think that `is_rvalue_reference` should not be implemented that way, because as you say, it will say "true" for `int&` – Andy Prowl Mar 27 '13 at 21:05
  • @AndyProwl I recommend to open a new question if you have doubt about it. – Johannes Schaub - litb Mar 27 '13 at 21:09
  • @JohannesSchaub-litb: OK, thank you for taking the time to help – Andy Prowl Mar 27 '13 at 21:11