6

I get a real kick out of exploring the unusual corners of C++. Having learned about the real types of functions rather than function pointers from this question, I tried messing around with function typing and came up with this bizarre case:

typedef int Func(int);

int Foo(int x) { return 1; }

int main()
{
    const Func*& f = &Foo;

    return 0;
}

Since &Foo is an rvalue of type Func*, I figured that I should be able to put it in a const reference, but I get this error from g++ 4.6:

funcTypes.cpp: In function ‘int main()’:
funcTypes.cpp:7:23: error: invalid initialization of non-const reference of type ‘int (*&)(int)’ from an rvalue of type ‘int (*)(int)’

But f is const! It has become apparent to me that the application of const to a function (or reference/reference to pointer etc.) is simply ignored; this code compiles just fine:

template <typename A, typename B>
struct SameType;

template <typename A>
struct SameType<A, A> { };

typedef int Func(int);

int main()
{
    SameType<const Func, Func>();

    return 0;
}

I'm guessing this is how boost pulls off their is_function type trait, but my question is - why does C++ allow this by ignoring it instead of forbidding it?

EDIT: I realise now that in the first example f is non-const and that const FuncPtr& f = &Foo does work. However, that was just background, the real question is the one above.

Community
  • 1
  • 1
voltrevo
  • 9,870
  • 3
  • 28
  • 33

6 Answers6

4

But f is const!

No, it's not. You're confusing

const Func*& f = &Foo;

with

Func* const& f = &Foo;

The former is a non-const ref to a const pointer. The latter is a const ref to a non-const pointer.

That's why I always write the const-ness before the */& rather than before the type. I would always write the first case as

Func const*& f = &Foo;

and then read right to left: reference to a pointer to a const Func.

smparkes
  • 13,807
  • 4
  • 36
  • 61
  • Are you sure: `const T &` is `T const&`, therefore, with `T = Func*`, I would say that `const Func*&` is `Func* const&` (a const pointer to a non-const `Func`) – Matthieu M. Dec 07 '11 at 09:37
  • 1
    Yes, I'm sure. All the other answers here say the same thing. That's why I believe `const`, when it's used, should always be immediately to the left of the \*/& it modifies. Leaves less room for this kind of confusion. Then you can always read right to left: foo const* const = const */pointer to const foo. – smparkes Dec 07 '11 at 09:43
  • right, sorry for the noise, this is precisely why the syntax is different than a plain text substitution in template parameters. – Matthieu M. Dec 07 '11 at 09:52
  • @Matthieu: Try the following nasty thing: typedef int* MyIntPointer; const MyIntPointer p; ++p; – Martin Dec 07 '11 at 10:02
  • Yeah I know :p My brain just derailed for a moment. I patched my ideone example also to show off that it works with your amended code: http://ideone.com/wvl2Z – Matthieu M. Dec 07 '11 at 10:21
  • Ah! You're right. See the edit I put on the question. I'm not all that familiar with those rules because I usually avoid raw pointers like the plague. Not because they're confusing, but because in quality modern C++ code there's little (not zero) need to explicitly mention raw pointers. – voltrevo Dec 07 '11 at 10:33
  • 1
    The rule is that `const` should be placed to the *right* of what it should modify, not to the left, since it applies to what is on its (the `const`s) *left side*. – Xeo Dec 07 '11 at 10:49
  • @Martin: Were you saying that `typedef int* MyIntPointer; const MyIntPointer p; ++p;` works? It failed just as I expected. I don't think that's nasty at all. – voltrevo Dec 07 '11 at 10:56
  • @Mozza314: True I just evaluated this as well. You can construct a combination of const and typedef that does sth. totally unexpected. – Martin Dec 07 '11 at 17:50
  • @Xeo: agreed. Poor wording in my comment (which I can't edit ...) – smparkes Dec 08 '11 at 18:35
3

In c++03 it was not ignored, but illformed (and was an sfinae case). I guess they changed that in c++11 because then you can simply have function parameters be const F& and can pass to it rvalue function objects aswell as normal functions.

See this DR which made the change http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#295

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • I'm not compiling with the -c++0x flag though. Are you saying this is one of those things where the compiler isn't required to give a diagnostic? – voltrevo Dec 07 '11 at 10:01
2

&Foo is a pointer. In general, I would suggest avoiding references to pointers (const or no). At least, not unless you know what you're doing.

So you should have:

const Func *f = &Foo;

Or really, you can ditch the const entirely:

Func *f = &Foo;

why does C++ allow this by ignoring it instead of forbidding it?

Because you're talking about two different things.

In C++, there is a difference between a function type and a function pointer. Foo is a function type, specifically int(int). &Foo is a function pointer, of type int(*)(int). A function type degrades into a function pointer, where necessary (much like array types degrade into pointers). But they are distinct (just like arrays).

So your two cases are not the same. Your first case is dealing with a function pointer, and your second case is dealing with a function type (which is what the template argument is deduced as).

As for why function types swallow the const, that's because the values of function types are already implicitly constant. You can't change them. The only operation you can perform on a function type is () (or conversion to function pointer). So a const T is equivalent to T if T is a function type. Visual Studio 2010 actually gives a warning about that.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • 1
    I think it's perfectly valid to have a reference to a pointer, although I agree it is tricky and you should know what you're doing. In fact the whole point is that the error should perhaps be the application of const in the first place, not that f is non-const. – voltrevo Dec 07 '11 at 09:11
  • @Mozza314: Note the edit. And yes, it is *valid* to have a reference to a pointer; that doesn't mean it's a good idea in general. – Nicol Bolas Dec 07 '11 at 09:16
  • I was going to say that const Func* should in fact be different to Func*, but you're right, they're the same too! I didn't expect const Func* f = Foo; f = Bar; to work, since it would be meaningful to prevent a const Func* from being assigned to a different function. Anyway, I think you're misunderstanding what I'm getting at - I know it's meaningless to distinguish Func and const Func. My point is that it is silently ignored instead of being an error. – voltrevo Dec 07 '11 at 10:10
  • In regards to the first part of my previous comment - oops; see the edit I put on the question. – voltrevo Dec 07 '11 at 10:29
1

The following compiles fine:

typedef int Func(int);
int Foo(int x) { return 1; }

int main()
{
  Func* const& f = &Foo;  //ok
  return 0;
}

Be aware that const statements are evaluated along with pointers and references from right to left. The last const to the very left you wrote translates to last possible position right of a Name (C++ FAQ on const placement). Hence

const Func*& f 

Is translated by the compiler to

Func const*& f 

Hence you get a reference to a constant pointer which is not what you want. Besides I would not use references to function pointer if you do not really have to.

iammilind
  • 68,093
  • 33
  • 169
  • 336
Martin
  • 4,738
  • 4
  • 28
  • 57
  • @Litb: True, I think I answered before the additional edit. But reading it again his code is still wrong, as I have pointed out in the edit and the fix solves his question. The code is valid and compiles – Martin Dec 07 '11 at 09:18
  • @JohannesSchaub-litb, May be `const Func*&` is a **non-const** reference and that's why a compiler error. However, `Func* const&` is a `const` reference. Hmm, the syntax for func-ptr is a bit confusing. – iammilind Dec 07 '11 at 09:18
  • @iammilind: True, but that still doesn't explain why `const Func` and `Func` are interpreted as the same type according to template instantiation rules. – Nicol Bolas Dec 07 '11 at 09:20
  • 1
    @Nicol and Litb: The adress of a function is an address on the the program memory and this layout is defined at compile time. It is as constant as a GOTO (shudder) label. Hence Nicols point is valid: You can't change the address of a function thus it is implicitly constant. – Martin Dec 07 '11 at 09:36
  • 1
    @martin sorry that is wrong. *the value* of a function is nonsense. a function has no value. – Johannes Schaub - litb Dec 07 '11 at 09:50
  • @JohannesSchaub-litb: A function name can be an implicit placeholder for the function pointer. Take a look at this:http://stackoverflow.com/questions/4328215/why-does-a-function-name-evaluate-to-true-in-c-and-how-to-get-warned-on-it – Martin Dec 07 '11 at 09:52
  • The address of a function isn't its value just as the address of an arrays first element is not the arrays' value. A pointer to the function and a pointer to an arrays first element would have thouse values respectively. – Johannes Schaub - litb Dec 07 '11 at 09:52
  • @JohannesSchaub-litb: Where did I write anything on a value of a function ?? A functions "name" can be a placeholder for the functions address – Martin Dec 07 '11 at 09:59
  • The address of an array (like any other objects address) is constant too and they too decay to their address. Do you say that `const` applied to arrays is ignored aswell? If not, what makes this case different? – Johannes Schaub - litb Dec 07 '11 at 10:00
  • @JohannesSchaub-litb: Thats a very good point. The only argument I could bring forth: You can set the address of an array at runtime using new (place the array on the heap). You cannot do this with functions. A function never resides on the heap hence it's address can be determined at compile time, which is not the case for dynamically allocated arrays – Martin Dec 07 '11 at 10:11
  • @curiousguy: I'm simply pointing out that the answer doesn't answer everything the person asked. – Nicol Bolas Dec 08 '11 at 18:56
0

No, f is not const. First of all, it is a reference to some mutable type (that mutable type happens to be a const pointer, i.e. a mutable pointer to something that you promise not to change through that particular pointer). However, with &Foo you are creating a temporary (of type Func*) and you cannot assign a temporary to a mutable reference. You can only assign it to a const reference. And that is precisely what the error message is telling you.

bitmask
  • 32,434
  • 14
  • 99
  • 159
0

Sometimes the error messages can be a bit cryptic.

I put together an example on ideone to illustrate the types printed by gcc for a variety of things:

#include <iostream>

typedef int(Func)(int);
typedef Func* FuncPtr;
typedef FuncPtr& FuncPtrRef;
typedef FuncPtr const& FuncPtrConstRef;

template <typename T> void DisplayType() { T::foo(); }

int main() {
  DisplayType<Func>();
  DisplayType<FuncPtr>();
  DisplayType<FuncPtrRef>();
  DisplayType<FuncPtrConstRef>();

}

The compilation errors give:

  • Func is of type int ()(int) (not a valid type, should now be fixed in gcc)
  • FuncPtr is of type int (*)(int)
  • FuncPtrRef is of type int (*&)(int)
  • FuncPtrConstRef is of type int (* const&)(int)

In your error message you have int (*&)(int), which is a reference to a non-const pointer to a function type.

You are not allowed to bind an rvalue to a non-const reference.

Note: this has thus nothing to do with const being swallowed, as smparkes correctly diagnosed

Community
  • 1
  • 1
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • I downvoted this first until i noticrd you just stated what the diagnistig fave.. you should mention that `int()(int)` is not a calid type. this gcc bug was fixed a while ago. – Johannes Schaub - litb Dec 07 '11 at 15:44
  • @JohannesSchaub-litb: Yes, I was not really attempting to answer the question (your answer adequately covers it), just trying to explain how untangle the diagnostic because it seems it confused a lot of readers (and the OP himself wasn't so savvy about it I think). I've edited the answer regarding the invalid type. – Matthieu M. Dec 07 '11 at 16:33