Re: [boost] [type_traits][function_types] Discard param const qualification, bug or feature?
Signature type int(int, const std::string) can be used as parameter for some compiletime algorithm. For example, algorithm that generate new signature type with optimal transfer arguments int (int, const std::string&) or generate signature with all argument references int (int&, const std::string&) Can't use type_traits, function_types at that cases, because const be omitted. This will lead to compiletime error, if function with first signature call function with second signature and use arguments from function with first signature for this call. Signature type void(int, const std::string) can use as types tuple for some compiletime algorithm. Can't use type_traits, function_types, at that case, because const be lost. Signature type void(int, const std::string) can use as parentheses dropper for "types list", e.g. for macro parameter. (int, const std::string) => int, const std::string. Can't use type_traits, function_types, at that case, because const be lost. I think default behavior is correct, but need parameter for change this behavior. At least function_types.
On 9/30/2013 10:44 AM, Quoth Sergey Zhuravlev:
Signature type int(int, const std::string) can be used as parameter for some compiletime algorithm. For example, algorithm that generate new signature type with optimal transfer arguments int (int, const std::string&) or generate signature with all argument references int (int&, const std::string&) Can't use type_traits, function_types at that cases, because const be omitted. This will lead to compiletime error, if function with first signature call function with second signature and use arguments from function with first signature for this call.
This was discussed recently; there is no difference between the following two signatures: int func(int a, std::string b); int func(int a, const std::string b); Both describe a function that accepts two parameters and returns an int value; the first parameter being an int passed by value and the second parameter being a std::string passed by value. "const" does nothing to any outside code -- its only effect is to artificially restrict the allowed behaviour inside the implementation of the function. In neither case can the implementation affect the original string object passed by the caller (unless the string's copy constructor is broken), so constness cannot be part of the interface. ie. it's perfectly valid to declare a function as the first in a header file and then implement it as the second in a cpp file. When the string is passed by reference, the presence or absence of "const" does make a difference, and it should be (and is) preserved. This *is* an interface detail as it indicates to the caller whether the object can be modified or not by the implementation. Can you describe (with code) what your actual problem is and why this is giving you trouble?
On Sun, 29 Sep 2013 14:44:52 -0700, Sergey Zhuravlev
Signature type int(int, const std::string) can be used as parameter for some compiletime algorithm. For example, algorithm that generate new signature type with optimal transfer arguments int (int, const std::string&) or generate signature with all argument references int (int&, const std::string&) Can't use type_traits, function_types at that cases, because const be omitted. This will lead to compiletime error, if function with first signature call function with second signature and use arguments from function with first signature for this call.
That was exactly the use case and the problem I had. Fortunately for me, I was able to specify the return type and parameters_type (as an mpl::vector) separately, which solved the issue. But I don't know if this is doable in general. For example, what does one do in the following scenario: template <typename T> struct Foo { void mybar( // Construct efficient argument transfer signature for T::bar ) { T::bar(....); } }; without forcing the user to seperately specify T::bar paramtypes as a separate template parameter?
Signature type void(int, const std::string) can use as types tuple for some compiletime algorithm. Can't use type_traits, function_types, at that case, because const be lost.
Signature type void(int, const std::string) can use as parentheses dropper for "types list", e.g. for macro parameter. (int, const std::string) => int, const std::string. Can't use type_traits, function_types, at that case, because const be lost.
I think default behavior is correct, but need parameter for change this behavior. At least function_types.
On Sep 29, 2013, at 11:00 PM, Mostafa
On Sun, 29 Sep 2013 14:44:52 -0700, Sergey Zhuravlev
wrote: Signature type int(int, const std::string) can be used as parameter for some compiletime algorithm. For example, algorithm that generate new signature type with optimal transfer arguments int (int, const std::string&) or generate signature with all argument references int (int&, const std::string&)
That was exactly the use case and the problem I had. Fortunately for me, I was able to specify the return type and parameters_type (as an mpl::vector) separately, which solved the issue. But I don't know if this is doable in general. For example, what does one do in the following scenario:
template <typename T> struct Foo { void mybar( // Construct efficient argument transfer signature for T::bar ) { T::bar(....); } };
without forcing the user to seperately specify T::bar paramtypes as a separate template parameter?
#include
On Mon, 30 Sep 2013 05:24:45 -0700, Rob Stewart
On Sep 29, 2013, at 11:00 PM, Mostafa
wrote: On Sun, 29 Sep 2013 14:44:52 -0700, Sergey Zhuravlev
wrote: Signature type int(int, const std::string) can be used as parameter for some compiletime algorithm. For example, algorithm that generate new signature type with optimal transfer arguments int (int, const std::string&) or generate signature with all argument references int (int&, const std::string&)
That was exactly the use case and the problem I had. Fortunately for me, I was able to specify the return type and parameters_type (as an mpl::vector) separately, which solved the issue. But I don't know if this is doable in general. For example, what does one do in the following scenario:
template <typename T> struct Foo { void mybar( // Construct efficient argument transfer signature for T::bar ) { T::bar(....); } };
without forcing the user to seperately specify T::bar paramtypes as a separate template parameter?
#include
... boost::call_traits<typename T::bar>::param_type
I should have been more specific, bar is some member function of T. So in Foo:mybar, the goal is to reconstruct T::bar's paramtypes as "efficient types". That's what the other person's post was also referring to. So if a client passes the following struct as a template parameter to Foo: struct ClientClass { static void bar(int const) { ... } }; the library is able to instantiate the following Foo::mybar void Foo::mybar(int const & x) { ClientClass::bar(x); }
On 30 September 2013 20:22, Mostafa wrote:
I should have been more specific, bar is some member function of T. So in Foo:mybar, the goal is to reconstruct T::bar's paramtypes as "efficient types". That's what the other person's post was also referring to. So if a client passes the following struct as a template parameter to Foo:
struct ClientClass { static void bar(int const) { ... } };
You're presenting this class as having a function of that type, but it doesn't, your example is: struct ClientClass { static void bar(int) { ... } };
the library is able to instantiate the following Foo::mybar
void Foo::mybar(int const & x) { ClientClass::bar(x); }
Why do you choose a different "efficient type" for an 'int' parameter vs a 'const int' parameter?
On Mon, 30 Sep 2013 12:49:44 -0700, Jonathan Wakely
On 30 September 2013 20:22, Mostafa wrote:
I should have been more specific, bar is some member function of T. So in Foo:mybar, the goal is to reconstruct T::bar's paramtypes as "efficient types". That's what the other person's post was also referring to. So if a client passes the following struct as a template parameter to Foo:
struct ClientClass { static void bar(int const) { ... } };
You're presenting this class as having a function of that type, but it doesn't, your example is:
struct ClientClass { static void bar(int) { ... } };
the library is able to instantiate the following Foo::mybar
void Foo::mybar(int const & x) { ClientClass::bar(x); }
Why do you choose a different "efficient type" for an 'int' parameter vs a 'const int' parameter?
You're most likely reading this message out of context. If you start with Sergey's response it'll probably make more sense.
On 30 September 2013 21:53, Mostafa wrote:
Why do you choose a different "efficient type" for an 'int' parameter vs a 'const int' parameter?
You're most likely reading this message out of context. If you start with Sergey's response it'll probably make more sense.
No, I've read the whole thread. It sounds like your code to generate signatures has a bug and doesn't model the rules of C++. I repeat: Why would you choose a different "efficient type" for an 'int' parameter vs a 'const int' parameter? I would say if you're doing that then you're doing something wrong, so should fix it to remove top-level const, because that's what C++ does and because it's probably the right thing to do anyway.
On Mon, 30 Sep 2013 14:20:40 -0700, Jonathan Wakely
On 30 September 2013 21:53, Mostafa wrote:
Why do you choose a different "efficient type" for an 'int' parameter vs a 'const int' parameter?
You're most likely reading this message out of context. If you start with Sergey's response it'll probably make more sense.
No, I've read the whole thread.
It sounds like your code to generate signatures has a bug and doesn't model the rules of C++.
I repeat: Why would you choose a different "efficient type" for an 'int' parameter vs a 'const int' parameter?
I would say if you're doing that then you're doing something wrong, so should fix it to remove top-level const, because that's what C++ does and because it's probably the right thing to do anyway.
Ah, ok, I had the use case reversed. Let's try this: struct SomeUserClass { static void foo(int const x) { SomeCodeGenClass::foo(x); } }; SomeCodeGenClass::foo is a mere parameter forwarder, so the goal is to do it as efficiently as possible. It's signature is constructed from SomeUserClass::foo. For correctness, that should be: void SomeCodeGenClass::foo(int const & x) But, function_typesSomeUserClass::foo::arg1_type resolves to int, so that add_reference'ing will give the following signature for the TMP constructed SomeCodeGenClass::foo void SomeCodeGenClass::foo(int & x) Which will give a compiler error for SomeUserClass::foo. Note, this is a really watered down example, so if function_types doesn't work with static member functions make the functions free, etc... And I'm not arguing that either type_traits or function_types should behave differently, I was wondering if a general solution exists for such a situation.
On 10/1/2013 2:46 PM, Quoth Mostafa:
struct SomeUserClass { static void foo(int const x) { SomeCodeGenClass::foo(x); } };
SomeCodeGenClass::foo is a mere parameter forwarder, so the goal is to do it as efficiently as possible. It's signature is constructed from SomeUserClass::foo. For correctness, that should be:
void SomeCodeGenClass::foo(int const & x)
This would also be the correct signature for foo(int x) -- because they're the same thing.
But, function_typesSomeUserClass::foo::arg1_type resolves to int, so that add_reference'ing will give the following signature for the TMP constructed SomeCodeGenClass::foo
void SomeCodeGenClass::foo(int & x)
A simple add_reference is obviously the wrong thing to be doing then. In general, you can take any parameter type T and wrap it as a "const T&" (or if you prefer, "T const&") and it will do the right thing, unless T was already a reference. But note that you must use a const reference -- a non-const reference won't work. Technically, if you're aiming for "most efficient format" you should also check whether the size of the type is less than the size of the reference (which is pointer-sized) and use the original type in that case. Though this is an optional improvement. You might also want to read up on "perfect forwarding", although this is only available in C++11.
On Mon, 30 Sep 2013 19:54:56 -0700, Gavin Lambert
On 10/1/2013 2:46 PM, Quoth Mostafa:
struct SomeUserClass { static void foo(int const x) { SomeCodeGenClass::foo(x); } };
SomeCodeGenClass::foo is a mere parameter forwarder, so the goal is to do it as efficiently as possible. It's signature is constructed from SomeUserClass::foo. For correctness, that should be:
void SomeCodeGenClass::foo(int const & x)
This would also be the correct signature for foo(int x) -- because they're the same thing.
But, function_typesSomeUserClass::foo::arg1_type resolves to int, so that add_reference'ing will give the following signature for the TMP constructed SomeCodeGenClass::foo
void SomeCodeGenClass::foo(int & x)
A simple add_reference is obviously the wrong thing to be doing then.
In general, you can take any parameter type T and wrap it as a "const T&" (or if you prefer, "T const&") and it will do the right thing, unless T was already a reference. But note that you must use a const reference -- a non-const reference won't work.
Not in my particular use case. The code I was working is generated by a mix of PPMP and TMP techniques. The argument x is eventually forwarded to some user block of code, where the user expects it to be of the same type as the one he/she specified in the signature of SomeUserClass::foo. So had the user specified this instead: struct SomeUserClass { static void foo(int x) { SomeCodeGenClass::foo(x); } }; Then it is expected that x would be mutable in that forwarded-to block of code. (The fact that it's actually a reference when it arrives there is immaterial to the user.) Now, I can make a copy of x before the final stage, but that would partially defeat the purpose of passing by reference, since a copy is already made at SomeUserClass::foo. In sum, this particular problem maybe restricted to PPMP/TMP code, and it maybe that pure TMP code doesn't suffer from this.
On 1 October 2013 05:45, Mostafa wrote:
On Mon, 30 Sep 2013 19:54:56 -0700, Gavin Lambert wrote:
A simple add_reference is obviously the wrong thing to be doing then.
Indeed.
In general, you can take any parameter type T and wrap it as a "const T&" (or if you prefer, "T const&") and it will do the right thing, unless T was already a reference. But note that you must use a const reference -- a non-const reference won't work.
Not in my particular use case. The code I was working is generated by a mix of PPMP and TMP techniques. The argument x is eventually forwarded to some user block of code, where the user expects it to be of the same type as the one he/she specified in the signature of SomeUserClass::foo. So had the user specified this instead:
struct SomeUserClass { static void foo(int x) { SomeCodeGenClass::foo(x); } };
Then it is expected that x would be mutable in that forwarded-to block of code.
Well then you're doing it wrong. You should base the forwarded parameter type on decltype(x) not the parameter type in the function signature. The function signature doesn't include the top-level const, that's how C++ works.
On Tue, 01 Oct 2013 00:30:02 -0700, Jonathan Wakely
On 1 October 2013 05:45, Mostafa wrote:
On Mon, 30 Sep 2013 19:54:56 -0700, Gavin Lambert wrote:
A simple add_reference is obviously the wrong thing to be doing then.
Indeed.
In general, you can take any parameter type T and wrap it as a "const T&" (or if you prefer, "T const&") and it will do the right thing, unless T was already a reference. But note that you must use a const reference -- a non-const reference won't work.
Not in my particular use case. The code I was working is generated by a mix of PPMP and TMP techniques. The argument x is eventually forwarded to some user block of code, where the user expects it to be of the same type as the one he/she specified in the signature of SomeUserClass::foo. So had the user specified this instead:
struct SomeUserClass { static void foo(int x) { SomeCodeGenClass::foo(x); } };
Then it is expected that x would be mutable in that forwarded-to block of code.
Well then you're doing it wrong. You should base the forwarded parameter type on decltype(x) not the parameter type in the function signature. The function signature doesn't include the top-level const, that's how C++ works.
Yup, and that's what I basically concluded way back in my response to Sergey's post. (Though decltype was not considered since I'm targeting C++03.)
On Sep 30, 2013, at 9:46 PM, Mostafa
On Mon, 30 Sep 2013 14:20:40 -0700, Jonathan Wakely
wrote: On 30 September 2013 21:53, Mostafa wrote:
Why do you choose a different "efficient type" for an 'int' parameter vs a 'const int' parameter?
You're most likely reading this message out of context. If you start with Sergey's response it'll probably make more sense.
No, I've read the whole thread.
It sounds like your code to generate signatures has a bug and doesn't model the rules of C++.
I repeat: Why would you choose a different "efficient type" for an 'int' parameter vs a 'const int' parameter?
Let me rephrase that. int and int const are passed from the caller to the function by value. What the function does with the parameters is immaterial to the caller, so why would you treat them differently when forwarding them?
I would say if you're doing that then you're doing something wrong, so should fix it to remove top-level const, because that's what C++ does and because it's probably the right thing to do anyway.
Ah, ok, I had the use case reversed. Let's try this:
struct SomeUserClass { static void foo(int const x) { SomeCodeGenClass::foo(x); } };
SomeCodeGenClass::foo is a mere parameter forwarder, so the goal is to do it as efficiently as possible. It's signature is constructed from SomeUserClass::foo. For correctness, that should be:
void SomeCodeGenClass::foo(int const & x)
No, that should be int x. The reference is a pessimization for int. For larger types, const & is appropriate, though it will apply regardless of the top-level constness of the parameter. IOW, if the type is cheap to copy, copy it, regardless of top-level constness. If it isn't, pass it by const &. That's what call_traits<T>::param_type does for you.
But, function_typesSomeUserClass::foo::arg1_type resolves to int, so that add_reference'ing will give the following signature for the TMP constructed SomeCodeGenClass::foo
void SomeCodeGenClass::foo(int & x)
Which will give a compiler error for SomeUserClass::foo. Note, this is a really watered down example, so if function_types doesn't work with static member functions make the functions free, etc...
And I'm not arguing that either type_traits or function_types should behave differently, I was wondering if a general solution exists for such a situation.
In the end, IIUC, you want a wrapper that determines the optimal argument forwarding scheme such that a user can wrap their function and then call the wrapper, as though it was their function, and get optimal argument forwarding. Is that right? Are you restricted to C++03 or is C++11 available? In either case, if I understand your goal, you're talking about perfect forwarding. It's the implementation that will differ. ___ Rob (Sent from my portable computation engine)
On Tue, 01 Oct 2013 03:22:10 -0700, Rob Stewart
On Sep 30, 2013, at 9:46 PM, Mostafa
wrote: On Mon, 30 Sep 2013 14:20:40 -0700, Jonathan Wakely
wrote: On 30 September 2013 21:53, Mostafa wrote:
Why do you choose a different "efficient type" for an 'int' parameter vs a 'const int' parameter?
You're most likely reading this message out of context. If you start with Sergey's response it'll probably make more sense.
No, I've read the whole thread.
It sounds like your code to generate signatures has a bug and doesn't model the rules of C++.
I repeat: Why would you choose a different "efficient type" for an 'int' parameter vs a 'const int' parameter?
Let me rephrase that. int and int const are passed from the caller to the function by value. What the function does with the parameters is immaterial to the caller, so why would you treat them differently when forwarding them?
See my last response to Gavin.
On 1 October 2013 05:22, Rob Stewart
No, that should be int x. The reference is a pessimization for int. For larger types, const & is appropriate, though it will apply regardless of the top-level constness of the parameter. IOW, if the type is cheap to copy, copy it, regardless of top-level constness. If it isn't, pass it by const &. That's what call_traits<T>::param_type does for you.
It's more complicated than that. When you pass by value the compiler is allowed to assume there are no aliases to your object. Put another way, passing by reference can still be a pessimization for types larger than an int (and that is even before copy elision). -- Nevin ":-)" Liber mailto:nevin@eviloverlord.com (847) 691-1404
On Oct 4, 2013, at 5:43 PM, Nevin Liber
On 1 October 2013 05:22, Rob Stewart
wrote: No, that should be int x. The reference is a pessimization for int. For larger types, const & is appropriate, though it will apply regardless of the top-level constness of the parameter. IOW, if the type is cheap to copy, copy it, regardless of top-level constness. If it isn't, pass it by const &. That's what call_traits<T>::param_type does for you.
It's more complicated than that. When you pass by value the compiler is allowed to assume there are no aliases to your object. Put another way, passing by reference can still be a pessimization for types larger than an int (and that is even before copy elision).
Granted, but I know of no way for a library to determine when pass-by-value would be better because of those optimizations. I suppose you could offer a wrapper type that indicates the desire for pass-by-value, even when call_traits would pass by reference. The developer could try that to see whether profiling shows improvement. ___ Rob (Sent from my portable computation engine)
participants (6)
-
Gavin Lambert
-
Jonathan Wakely
-
Mostafa
-
Nevin Liber
-
Rob Stewart
-
Sergey Zhuravlev