[type_traits][function_types] Discard param const qualification, bug or feature?
Both the type_traits and function_types library discard parameter
const-qualifiers when decomposing function types. Is this a bug or a
feature? If a feature, then it's surprising enough behaviour that I think
it warrants some documentation in both libraries. For the record, I'm
using gcc 4.7.3 on Cygwin 1.7.22 in Windows 7, and here's a reproducible
example of what I'm talking about:
#include
2013/9/25 Mostafa
Both the type_traits and function_types library discard parameter const-qualifiers when decomposing function types. Is this a bug or a feature? If a feature, then it's surprising enough behaviour that I think it warrants some documentation in both libraries. For the record, I'm using gcc 4.7.3 on Cygwin 1.7.22 in Windows 7, and here's a reproducible example of what I'm talking about:
#include
#include #include #include #include using namespace boost;
typedef void (foo)(int const);
int main() {
typedef function_traits<foo>::arg1_**type traits_deduced_type; BOOST_MPL_ASSERT(( is_same
)); //Errors: //BOOST_MPL_ASSERT(( is_same )); typedef boost::function_types::**parameter_types<foo> ftypes_params; typedef boost::mpl::at
>::type ftypes_deduced_type; BOOST_MPL_ASSERT(( is_same )); //Errors: //BOOST_MPL_ASSERT(( is_same )); return 0; }
Mostafa
The two function declarations: void f(int); void f(int const); declare the same function, not an overload. Const added to an argument type passed by value doesn't change the function signature. It is because the parameter will be copied anyway, and the const refers to how the argument will be treated inside the function's implementation. So in your example, I think the compiler correctly discards the 'const'. Regards, Kris
On Sep 25, 2013, at 2:57 AM, Krzysztof Czainski <1czajnik@gmail.com> wrote:
2013/9/25 Mostafa
Both the type_traits and function_types library discard parameter const-qualifiers when decomposing function types. Is this a bug or a feature? If a feature, then it's surprising enough behaviour that I think it warrants some documentation in both libraries.
[snip]
typedef void (foo)(int const);
The two function declarations: void f(int); void f(int const); declare the same function, not an overload.
Right. Top level const is not part of the function's type.
Const added to an argument type passed by value doesn't change the function signature. It is because the parameter will be copied anyway, and the const refers to how the argument will be treated inside the function's implementation.
Exactly. ___ Rob (Sent from my portable computation engine)
On Wed, Sep 25, 2013 at 12:48 PM, Rob Stewart
On Sep 25, 2013, at 2:57 AM, Krzysztof Czainski <1czajnik@gmail.com> wrote:
2013/9/25 Mostafa
Both the type_traits and function_types library discard parameter const-qualifiers when decomposing function types. Is this a bug or a feature? If a feature, then it's surprising enough behaviour that I think it warrants some documentation in both libraries.
[snip]
typedef void (foo)(int const);
The two function declarations: void f(int); void f(int const); declare the same function, not an overload.
Right. Top level const is not part of the function's type.
Const added to an argument type passed by value doesn't change the function signature. It is because the parameter will be copied anyway, and the const refers to how the argument will be treated inside the function's implementation.
Exactly.
On Wed, 25 Sep 2013 02:09:19 -0700, Andrey Semashev
On Wed, Sep 25, 2013 at 12:48 PM, Rob Stewart
wrote: On Sep 25, 2013, at 2:57 AM, Krzysztof Czainski <1czajnik@gmail.com> wrote:
2013/9/25 Mostafa
Both the type_traits and function_types library discard parameter const-qualifiers when decomposing function types. Is this a bug or a feature? If a feature, then it's surprising enough behaviour that I think it warrants some documentation in both libraries.
[snip]
typedef void (foo)(int const);
The two function declarations: void f(int); void f(int const); declare the same function, not an overload.
Right. Top level const is not part of the function's type.
Const added to an argument type passed by value doesn't change the function signature. It is because the parameter will be copied anyway, and the const refers to how the argument will be treated inside the function's implementation.
Exactly.
Ah, thanks. I was only thinking in terms of the parameter type.
I have to say that while these rules are logical and understandable when explained, types of function arguments are a constant source of confusion.
Exactly. That's why an explanatory note or two in the documentation would go a long way.
The described above quirk with const arguments can be considered quite rare as people usually don't declare arguments that way.
It can show up in some meta-programming code. Say you want to capture a value by const and let the meta-programmed code forward it to some destination, and for the sake of efficiency you decided to the forwarding by reference. If you're not careful, you can end up with an 'int &' to an 'int const' in the meta-programmed code. Mostafa
On Sep 25, 2013, at 10:59 AM, Mostafa
On Wed, 25 Sep 2013 02:09:19 -0700, Andrey Semashev
wrote: I have to say that while these rules are logical and understandable when explained, types of function arguments are a constant source of confusion.
Exactly. That's why an explanatory note or two in the documentation would go a long way.
Offer a documentation patch and it might be applied.
The described above quirk with const arguments can be considered quite rare as people usually don't declare arguments that way.
It can show up in some meta-programming code. Say you want to capture a value by const and let the meta-programmed code forward it to some destination, and for the sake of efficiency you decided to the forwarding by reference. If you're not careful, you can end up with an 'int &' to an 'int const' in the meta-programmed code.
You can create an int & to a copy of an int const, but not to an int const. ___ Rob (Sent from my portable computation engine)
On Sep 25, 2013, at 5:09 AM, Andrey Semashev
On Wed, Sep 25, 2013 at 12:48 PM, Rob Stewart
wrote: On Sep 25, 2013, at 2:57 AM, Krzysztof Czainski <1czajnik@gmail.com> wrote:
2013/9/25 Mostafa
Both the type_traits and function_types library discard parameter const-qualifiers when decomposing function types. Is this a bug or a feature? If a feature, then it's surprising enough behaviour that I think it warrants some documentation in both libraries.
[snip]
typedef void (foo)(int const);
The two function declarations: void f(int); void f(int const); declare the same function, not an overload.
Right. Top level const is not part of the function's type.
Const added to an argument type passed by value doesn't change the function signature. It is because the parameter will be copied anyway, and the const refers to how the argument will be treated inside the function's implementation.
Exactly.
I have to say that while these rules are logical and understandable when explained, types of function arguments are a constant source of confusion.
There are many things about C++ that are contrary to someone's intuition. That parameter declaration is no different than the following variable: int const i; When used with either of the forms of f(), above, does i work any differently than j, below? int j; They work alike with either f(), because the int is copied. The difference is whether you can change the int after initialization. The parameters are no different.
The described above quirk with const arguments can be considered quite rare as people usually don't declare arguments that way.
Maybe you're right, but I always do so, by default.
But C++11 brought us rvalue references, and the following:
foo(int&& n) { // n is _not_ rvalue reference here }
I understand the rationale for this, and it seems the right thing. But once in a while, when yet another fellow developer asks me why is that so, I wonder if it would be better if the standard was more straight-forward in this part.
The issue comes down to one of consequences. If n were still an rvalue, within foo(), even when referenced by name, what problems will that cause? ___ Rob (Sent from my portable computation engine)
On Thu, Sep 26, 2013 at 1:28 PM, Rob Stewart
On Sep 25, 2013, at 5:09 AM, Andrey Semashev
wrote: I have to say that while these rules are logical and understandable when explained, types of function arguments are a constant source of confusion.
There are many things about C++ that are contrary to someone's intuition. That parameter declaration is no different than the following variable:
int const i;
When used with either of the forms of f(), above, does i work any differently than j, below?
int j;
They work alike with either f(), because the int is copied. The difference is whether you can change the int after initialization. The parameters are no different.
Sure, they work the same, from the caller's perspective. But is that a reason to cheat with the type system? void foo(int); void bar(const int); typeid(&foo) == typeid(&bar); // why? My point was that despite the same behavior on the caller's side, the functions have different signatures, and I don't see why there was a _necessity_ to force the compiler to drop cv-qualifiers from the function argument types. In other words, it makes the language more complicated for no apparent reason.
But C++11 brought us rvalue references, and the following:
foo(int&& n) { // n is _not_ rvalue reference here }
I understand the rationale for this, and it seems the right thing. But once in a while, when yet another fellow developer asks me why is that so, I wonder if it would be better if the standard was more straight-forward in this part.
The issue comes down to one of consequences. If n were still an rvalue, within foo(), even when referenced by name, what problems will that cause?
AFAIR, the motivating example was something like this: void bar(int&& n); // moves from n void foo(int&& n) { bar(n); bar(n); // moves from a moved-from object ++n; // uses a moved-from object } This can be a real gotcha, I admit. But just as well as this: class my_class { vector<int> vec; public: my_class(vector<int>&& v) : vec(v) // copies the vector, instead of moving { } }; This latter mistake is usually less critical, but for that reason it is also more often made and left unnoticed.
On 26 September 2013 11:02, Andrey Semashev wrote:
Sure, they work the same, from the caller's perspective. But is that a reason to cheat with the type system?
void foo(int); void bar(const int);
typeid(&foo) == typeid(&bar); // why?
My point was that despite the same behavior on the caller's side, the functions have different signatures,
No they don't. "Signature" has a specific meaning in C++ and they have the same signature. They have different tokens in the declaration, but so do these: signed long int i; long j; That doesn't mean they have different types.
and I don't see why there was a _necessity_ to force the compiler to drop cv-qualifiers from the function argument types. In other words, it makes the language more complicated for no apparent reason.
There are reasons. If they were different signatures would you be able to overload based on top-level const-ness of parameters? Would that be useful? If the function takes its arguments by-value why do you (the caller) care what it does with that parameter? It should be able to modify it or not, without that affecting the function signature the caller sees. Whether the parameter is const or not is an internal detail to the function, not part of the interface. Currently template argument deduction is constistent, and doesn't deduce a top-level const. If functions didn't ignore top-level const on parameters would argument deduction have to change to deduce top-level const-ness? e.g. template<typename T> void f(T by_value) { ++by_value; } int i; const int ci; f(i); f(ci); Should that call two different template specializations? Doing so would break the function template in the f(ci) case because T would be 'const int' and non-modifiable.
On Thu, Sep 26, 2013 at 2:40 PM, Jonathan Wakely
On 26 September 2013 11:02, Andrey Semashev wrote:
Sure, they work the same, from the caller's perspective. But is that a reason to cheat with the type system?
void foo(int); void bar(const int);
typeid(&foo) == typeid(&bar); // why?
My point was that despite the same behavior on the caller's side, the functions have different signatures,
No they don't. "Signature" has a specific meaning in C++ and they have the same signature.
Ok, I used the term "signature" for the lack of a better word.
They have different tokens in the declaration, but so do these:
signed long int i; long j;
That doesn't mean they have different types.
Yes, but that's not the case with long and const long, is it?
and I don't see why there was a _necessity_ to force the compiler to drop cv-qualifiers from the function argument types. In other words, it makes the language more complicated for no apparent reason.
There are reasons.
If they were different signatures would you be able to overload based on top-level const-ness of parameters?
The functions with different qualification of arguments would be considered distinct, yes. Whether or not such declarations would result in compile time error is another question.
Would that be useful?
No, the overload would be always ambiguous. Because of this it would probably be a good idea to always generate an error when such overloads are found.
If the function takes its arguments by-value why do you (the caller) care what it does with that parameter?
No, and as I said, the top level cv-qualification of function arguments would still be transparent for the caller. Unless he wants to know the exact "signature" (I'm using the term loosely here again) of the function.
It should be able to modify it or not, without that affecting the function signature the caller sees. Whether the parameter is const or not is an internal detail to the function, not part of the interface.
True. I'm not really arguing with that. It's just that the outcome of this reasoning appears unexpected for newcomers.
Currently template argument deduction is constistent, and doesn't deduce a top-level const. If functions didn't ignore top-level const on parameters would argument deduction have to change to deduce top-level const-ness? e.g.
template<typename T> void f(T by_value) { ++by_value; }
int i; const int ci;
f(i); f(ci);
Should that call two different template specializations? Doing so would break the function template in the f(ci) case because T would be 'const int' and non-modifiable.
No, both calls would invoke f<int>(int) with signature void(int). However, if f was declared like this: template< typename T > void f(const T by_value); then both calls would invoke f<int>(const int) with signature void(const int).
On 26 September 2013 12:50, Andrey Semashev wrote:
On Thu, Sep 26, 2013 at 2:40 PM, Jonathan Wakely
wrote: It should be able to modify it or not, without that affecting the function signature the caller sees. Whether the parameter is const or not is an internal detail to the function, not part of the interface.
True. I'm not really arguing with that.
But that's the fundamental reason it's not part of the signature!
It's just that the outcome of this reasoning appears unexpected for newcomers.
If the language changed every time a newcomer finds something unexpected the standard would be revised daily.
participants (5)
-
Andrey Semashev
-
Jonathan Wakely
-
Krzysztof Czainski
-
Mostafa
-
Rob Stewart