Unnecessary const referencing
-
Over in https://forum.qt.io/topic/146160/unresolved-external-symbol-error OP declared:
template<typename T> bool Numbers::isPerfectPower(const T& number, const int &n)and then proceeds to instantiate it with
Tas e.g.double.On the basis of the C++ mantra of "why write 1 token when you can write 3", is it now the C++ "thing" that we pass numbers as
const type &when we could write plaintype? I would have writtentemplate<typename T> bool Numbers::isPerfectPower(T number, int n)For the
Tunless it's some "complex numeric type", such as perhaps an imaginary or unlimited precision number, why should I preferconst T &overThere? And certainly as for the second argument, whyconst int &overint?Is this just the modern C++ style? Is there some reason why it's more suitable here perhaps in a template? In general do you pass
ints asconst int &? Or do people just like typing a lot?@JonB there is no reason to pass an int as const ref, it is in fact more work and memory intensive than simply copying it over.
Maybe if you're working on a microcontroller that works with 2 byte pointers and you're passing an int64 around, maybe.
if your template parameter is supposed to be only an interagier of some kind I would remove the const ref and make an static assert type check.
If your function also accepts complex types, than yes, const ref it.
IMHO
-
Over in https://forum.qt.io/topic/146160/unresolved-external-symbol-error OP declared:
template<typename T> bool Numbers::isPerfectPower(const T& number, const int &n)and then proceeds to instantiate it with
Tas e.g.double.On the basis of the C++ mantra of "why write 1 token when you can write 3", is it now the C++ "thing" that we pass numbers as
const type &when we could write plaintype? I would have writtentemplate<typename T> bool Numbers::isPerfectPower(T number, int n)For the
Tunless it's some "complex numeric type", such as perhaps an imaginary or unlimited precision number, why should I preferconst T &overThere? And certainly as for the second argument, whyconst int &overint?Is this just the modern C++ style? Is there some reason why it's more suitable here perhaps in a template? In general do you pass
ints asconst int &? Or do people just like typing a lot?@JonB For POD types, passing a copy
(int number, int n)is enough. A const reference in such case is needlessly complicating stuff for no gain at all.What I often do for POD types, however, is to pass them as const without a reference
(const number, const int n). This offers no advantage in terms of performance but makes it kind of clear that these are input parameters and that they will not change. -
@JonB For POD types, passing a copy
(int number, int n)is enough. A const reference in such case is needlessly complicating stuff for no gain at all.What I often do for POD types, however, is to pass them as const without a reference
(const number, const int n). This offers no advantage in terms of performance but makes it kind of clear that these are input parameters and that they will not change.@sierdzio said in Unnecessary const referencing:
makes it kind of clear that these are input parameters and that they will not change
Also, this way, you make sure they are not changed by accident.
-
@JonB For POD types, passing a copy
(int number, int n)is enough. A const reference in such case is needlessly complicating stuff for no gain at all.What I often do for POD types, however, is to pass them as const without a reference
(const number, const int n). This offers no advantage in terms of performance but makes it kind of clear that these are input parameters and that they will not change.@sierdzio said in Unnecessary const referencing:
What I often do for POD types, however, is to pass them as const without a reference (const number, const int n). This offers no advantage in terms of performance but makes it kind of clear that these are input parameters and that they will not change.
I actually like to do that too, but some code models / compilers actually give you a warning about this
So I usually end up with the declaration of
void constArgument(int someArgument);and a definition of
void SomeClass::constArgument(const int someArgument) { qDebug() << someArgument; } -
@sierdzio said in Unnecessary const referencing:
What I often do for POD types, however, is to pass them as const without a reference (const number, const int n). This offers no advantage in terms of performance but makes it kind of clear that these are input parameters and that they will not change.
I actually like to do that too, but some code models / compilers actually give you a warning about this
So I usually end up with the declaration of
void constArgument(int someArgument);and a definition of
void SomeClass::constArgument(const int someArgument) { qDebug() << someArgument; } -
@J-Hilk
And why C++ allows you to have a different definition from declaration for this case is beyond me.... -
J JonB has marked this topic as solved on
-
Over in https://forum.qt.io/topic/146160/unresolved-external-symbol-error OP declared:
template<typename T> bool Numbers::isPerfectPower(const T& number, const int &n)and then proceeds to instantiate it with
Tas e.g.double.On the basis of the C++ mantra of "why write 1 token when you can write 3", is it now the C++ "thing" that we pass numbers as
const type &when we could write plaintype? I would have writtentemplate<typename T> bool Numbers::isPerfectPower(T number, int n)For the
Tunless it's some "complex numeric type", such as perhaps an imaginary or unlimited precision number, why should I preferconst T &overThere? And certainly as for the second argument, whyconst int &overint?Is this just the modern C++ style? Is there some reason why it's more suitable here perhaps in a template? In general do you pass
ints asconst int &? Or do people just like typing a lot?There's nothing "technically" wrong here. The problem here is bad API design. On one hand it tries to be generic, so it doesn't restrict the type and uses const& for cases where you pass some "big" type that benefits from passing by reference. On the other it pessimizes the actually intended use case, which are the basic numeric types. So basically it's the worst of both worlds. It's optimized for case it won't be used for.
A good API design would first analyze use cases. If it's just for the basic types you can restrict the allowed types with something likestd::is_arithmeticor a concept and pass by value. If it's for any type, but you don't want to loose on performance for any case then provide an overload for the two ways. -
There's nothing "technically" wrong here. The problem here is bad API design. On one hand it tries to be generic, so it doesn't restrict the type and uses const& for cases where you pass some "big" type that benefits from passing by reference. On the other it pessimizes the actually intended use case, which are the basic numeric types. So basically it's the worst of both worlds. It's optimized for case it won't be used for.
A good API design would first analyze use cases. If it's just for the basic types you can restrict the allowed types with something likestd::is_arithmeticor a concept and pass by value. If it's for any type, but you don't want to loose on performance for any case then provide an overload for the two ways.What's wrong with
template <typename T> bool Numbers::isPerfectPower(T &&number, int n);:)
-
What's wrong with
template <typename T> bool Numbers::isPerfectPower(T &&number, int n);:)
-
-
@Chris-Kawa said in Unnecessary const referencing:
@Christian-Ehrlicher said:
What's wrong with
Of course one would do something like this, instead:
template <typename T> std::remove_reference_t<T> foo1(T&& a) { return std::forward<T>(a) + 42; }https://godbolt.org/z/8nT5verW4
Granted, it still misses the opportunity to use
leainstead of resolving the address throughmovbut works with whatever you pass it. I'd argue that for numeric/primitive types the compiler should expand a const lvalue to a copy at the optimizer pass, but it apparently doesn't. -
@Chris-Kawa said in Unnecessary const referencing:
@Christian-Ehrlicher said:
What's wrong with
Of course one would do something like this, instead:
template <typename T> std::remove_reference_t<T> foo1(T&& a) { return std::forward<T>(a) + 42; }https://godbolt.org/z/8nT5verW4
Granted, it still misses the opportunity to use
leainstead of resolving the address throughmovbut works with whatever you pass it. I'd argue that for numeric/primitive types the compiler should expand a const lvalue to a copy at the optimizer pass, but it apparently doesn't.@kshegunov
Why write this getting-more-complex-all-the-time code if you can instead write:template <typename T> bool Numbers::isPerfectPower(T number, int n);(given usage for POD numerics)? I just don't get how having to know about and add in
std::remove_reference_t<T> std::forward<T>is anything other than making C++ harder. I like C++, but what you're asking me, the programmer, to remember is getting out of hand.....
-
@kshegunov
Why write this getting-more-complex-all-the-time code if you can instead write:template <typename T> bool Numbers::isPerfectPower(T number, int n);(given usage for POD numerics)? I just don't get how having to know about and add in
std::remove_reference_t<T> std::forward<T>is anything other than making C++ harder. I like C++, but what you're asking me, the programmer, to remember is getting out of hand.....
@JonB said:
Why write this getting-more-complex-all-the-time code if you can instead write
Because nothing in that syntax prevents you from passing e.g. a BigNum class instance, that would be heavily penalized for using a by value parameter (copy).
That's why I mentioned restricting possible inputs e.g.
template<typename T> concept ByValueType = std::is_arithmetic_v<T>; template <ByValueType T> bool Numbers::isPerfectPower(T number, int n);or whatever other condition you think is appropriate, like checking the
sizeofof the type to see if it fits in a register or using some custom type traits to separate types that should be passed by value from those that benefit from referencing.Of course all of this discussion is academic given how wasteful the actual implementation of the OPs function is. Any benefits we come up on the parameter front are immediately buried just few lines into the function body, but hey, why not :)
-
@Chris-Kawa said in Unnecessary const referencing:
@Christian-Ehrlicher said:
What's wrong with
Of course one would do something like this, instead:
template <typename T> std::remove_reference_t<T> foo1(T&& a) { return std::forward<T>(a) + 42; }https://godbolt.org/z/8nT5verW4
Granted, it still misses the opportunity to use
leainstead of resolving the address throughmovbut works with whatever you pass it. I'd argue that for numeric/primitive types the compiler should expand a const lvalue to a copy at the optimizer pass, but it apparently doesn't.@kshegunov Sure, but that's kinda another topic. What you show is just the intended benefit of small perfectly forwarded functions i.e. they completely disappear via inlining. That wouldn't happen though in a longer function, like what the OP has, so the benefit you show is indeed valid, but not really related to the topic at hand. Notice that you're actually doing what I proposed i.e. have an overload for copy and ref variants. The inlined && forward is just an unrelated complication for style points.
Btw. I love how the float version transforms into a fixed point xmm arithmetic with magic constant. Compilers are so smart these days :)
-
@kshegunov Sure, but that's kinda another topic. What you show is just the intended benefit of small perfectly forwarded functions i.e. they completely disappear via inlining. That wouldn't happen though in a longer function, like what the OP has, so the benefit you show is indeed valid, but not really related to the topic at hand. Notice that you're actually doing what I proposed i.e. have an overload for copy and ref variants. The inlined && forward is just an unrelated complication for style points.
Btw. I love how the float version transforms into a fixed point xmm arithmetic with magic constant. Compilers are so smart these days :)
@Chris-Kawa said in Unnecessary const referencing:
Notice that you're actually doing what I proposed i.e. have an overload for copy and ref variants.
No argument there.
The inlined && forward is just an unrelated complication for style points.
As far as we are talking
+ 42, if for example the body was something like:template <typename T> std::remove_reference_t<T> foo1(T&& a) { return smthng(std::forward<T>(a)) + 42; }Where
smthngis some other templated nonsense (or simply overloaded), then, I'm sure you'd agree the forward is no longer redundant. :)Btw. I love how the float version transforms into a fixed point xmm arithmetic with magic constant. Compilers are so smart these days :)
Yeah, true enough. Although you can still catch them missing optimization opportunities from time to time.
@JonB said in Unnecessary const referencing:
Why write this getting-more-complex-all-the-time code if you can ...
What Chris said (... and since this is indeed an academic discussion at this point).